package com.zioinfo.mes.spc;

import com.zioinfo.mes.admin.AuditService;
import com.zioinfo.mes.ai.AiService;
import com.zioinfo.mes.spc.mapper.SpcMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * SPC 서비스 — 관리도(Xbar-R) 부분군 적재 + 관리한계 산출 + 공정능력(Cp/Cpk) + AI 이상감지.
 *
 * <p>Xbar-R 상수(부분군 크기 n=2~10): A2, D3, D4, d2 표 내장.
 * 공정능력: Cp = (USL-LSL)/(6σ), Cpk = min((USL-μ),(μ-LSL))/(3σ), σ ≈ Rbar/d2.
 */
@Service
@RequiredArgsConstructor
public class SpcService {

    // n: {A2, D3, D4, d2}  (Xbar-R 관리도 상수표)
    private static final Map<Integer, double[]> CONST = Map.of(
        2, new double[]{1.880, 0.0, 3.267, 1.128},
        3, new double[]{1.023, 0.0, 2.574, 1.693},
        4, new double[]{0.729, 0.0, 2.282, 2.059},
        5, new double[]{0.577, 0.0, 2.114, 2.326},
        6, new double[]{0.483, 0.0, 2.004, 2.534},
        7, new double[]{0.419, 0.076, 1.924, 2.704},
        8, new double[]{0.373, 0.136, 1.864, 2.847},
        9, new double[]{0.337, 0.184, 1.816, 2.970},
        10, new double[]{0.308, 0.223, 1.777, 3.078}
    );

    private final SpcMapper mapper;
    private final AiService ai;
    private final AuditService audit;

    public List<String> charts() {
        return mapper.distinctCharts();
    }

    public List<MesSpcSample> samples(String chartCode, int limit) {
        return mapper.findByChart(chartCode, limit <= 0 ? 100 : limit);
    }

    /** 부분군 측정값 적재 — 평균/범위 자동 계산. */
    @Transactional
    public MesSpcSample addSample(String chartCode, String itemCode, String characteristic,
                                  Integer subgroupNo, List<Double> values, String actor) {
        if (values == null || values.isEmpty())
            throw new IllegalArgumentException("ERR-SPC-400: 측정값 필수");
        double mean = values.stream().mapToDouble(Double::doubleValue).average().orElse(0);
        double max = values.stream().mapToDouble(Double::doubleValue).max().orElse(0);
        double min = values.stream().mapToDouble(Double::doubleValue).min().orElse(0);
        MesSpcSample s = new MesSpcSample();
        s.setChartCode(chartCode);
        s.setItemCode(itemCode);
        s.setCharacteristic(characteristic);
        s.setSubgroupNo(subgroupNo);
        s.setSampleValues(values.toString());
        s.setSampleMean(round3(mean));
        s.setSampleRange(round3(max - min));
        s.setCreatedBy(actor);
        mapper.insert(s);
        audit.log("SPC_SAMPLE", chartCode, "subgroup=" + subgroupNo + " mean=" + s.getSampleMean());
        return s;
    }

    /**
     * 관리도 계산 — Xbar/R 관리한계 + Cp/Cpk + AI 이상감지.
     *
     * @param lsl,usl 규격(공정능력 계산용, 없으면 Cp/Cpk 생략)
     */
    public Map<String, Object> chart(String chartCode, Double lsl, Double usl) {
        List<MesSpcSample> samples = mapper.findByChart(chartCode, 200);
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("chartCode", chartCode);
        out.put("subgroupCount", samples.size());
        if (samples.isEmpty()) {
            out.put("message", "데이터 없음");
            return out;
        }
        int n = estimateSubgroupSize(samples);
        double[] c = CONST.getOrDefault(n, CONST.get(5));
        double a2 = c[0], d3 = c[1], d4 = c[2], d2 = c[3];

        List<Double> means = new ArrayList<>();
        List<Double> ranges = new ArrayList<>();
        for (MesSpcSample s : samples) {
            if (s.getSampleMean() != null) means.add(s.getSampleMean());
            if (s.getSampleRange() != null) ranges.add(s.getSampleRange());
        }
        double xbar = means.stream().mapToDouble(Double::doubleValue).average().orElse(0);
        double rbar = ranges.stream().mapToDouble(Double::doubleValue).average().orElse(0);

        double xUcl = round3(xbar + a2 * rbar);
        double xLcl = round3(xbar - a2 * rbar);
        double rUcl = round3(d4 * rbar);
        double rLcl = round3(d3 * rbar);

        out.put("subgroupSize", n);
        out.put("xbar", round3(xbar));
        out.put("rbar", round3(rbar));
        out.put("xbarUcl", xUcl);
        out.put("xbarLcl", xLcl);
        out.put("rUcl", rUcl);
        out.put("rLcl", rLcl);
        out.put("means", means);
        out.put("ranges", ranges);

        // 공정능력 Cp/Cpk (σ ≈ Rbar/d2)
        if (lsl != null && usl != null && d2 > 0 && rbar > 0) {
            double sigma = rbar / d2;
            double cp = (usl - lsl) / (6 * sigma);
            double cpkU = (usl - xbar) / (3 * sigma);
            double cpkL = (xbar - lsl) / (3 * sigma);
            double cpk = Math.min(cpkU, cpkL);
            out.put("sigma", round3(sigma));
            out.put("cp", round3(cp));
            out.put("cpk", round3(cpk));
            out.put("capabilityGrade", cpk >= 1.33 ? "충분(≥1.33)" : cpk >= 1.0 ? "보통(1.0~1.33)" : "부족(<1.0)");
        }

        // AI 이상감지(Western Electric 규칙 — Ollama 폴백 동일 동작)
        Map<String, Object> anomaly = ai.spcAnomaly(means, xUcl, xLcl, xbar);
        out.put("anomaly", anomaly.get("anomaly"));
        out.put("violations", anomaly.get("violations"));
        return out;
    }

    /** 부분군 크기 추정 — sample_values 배열 길이의 최빈치 근사(평균 길이). */
    private int estimateSubgroupSize(List<MesSpcSample> samples) {
        int sum = 0, cnt = 0;
        for (MesSpcSample s : samples) {
            String v = s.getSampleValues();
            if (v == null) continue;
            int commas = 0;
            for (int i = 0; i < v.length(); i++) if (v.charAt(i) == ',') commas++;
            int size = commas + 1;
            if (size >= 2) { sum += size; cnt++; }
        }
        int avg = cnt == 0 ? 5 : Math.round((float) sum / cnt);
        return Math.min(10, Math.max(2, avg));
    }

    private double round3(double d) {
        return Math.round(d * 1000.0) / 1000.0;
    }
}
