package com.zioinfo.kintex.module.m3;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.module.m3.dto.DesignPlanDto;
import com.zioinfo.kintex.module.m3.dto.DesignSaveRequest;
import com.zioinfo.kintex.module.m3.dto.DesignSpec;
import com.zioinfo.kintex.module.m3.mapper.DesignMapper;
import com.zioinfo.kintex.rules.ComplianceReport;
import com.zioinfo.kintex.rules.ComplianceRuleEngine;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * M3 서비스 — DesignMapper 배선 완료. 설계 조회/저장(spec jsonb)·규정 사전검증(precheck) 수행.
 * precheck는 스펙만으로 즉시 평가(무영속), 저장은 버전 upsert 후 최신본을 반환한다.
 */
@Service
public class DesignServiceImpl implements DesignService {

    private final DesignMapper designMapper;
    private final ComplianceRuleEngine ruleEngine;
    private final ObjectMapper objectMapper;

    public DesignServiceImpl(DesignMapper designMapper, ComplianceRuleEngine ruleEngine,
                             ObjectMapper objectMapper) {
        this.designMapper = designMapper;
        this.ruleEngine = ruleEngine;
        this.objectMapper = objectMapper;
    }

    @Override
    public DesignPlanDto getDesign(String boothId, Integer version) {
        Map<String, Object> row = designMapper.findDesign(boothId, version);
        if (row == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "설계안을 찾을 수 없습니다.");
        }
        return toDto(row);
    }

    @Override
    @Transactional
    public DesignPlanDto saveDesign(String boothId, DesignSaveRequest request) {
        int version = request.version() != null ? request.version() : nextVersion(boothId);
        Map<String, Object> params = new LinkedHashMap<>();
        params.put("boothId", boothId);
        params.put("version", version);
        params.put("status", "draft");
        params.put("specJson", writeSpec(request.spec()));
        designMapper.upsertDesign(params);

        Map<String, Object> row = designMapper.findDesign(boothId, version);
        if (row == null) {
            throw new ApiException(ErrorCode.INTERNAL, "설계안 저장 결과를 확인할 수 없습니다.");
        }
        return toDto(row);
    }

    private int nextVersion(String boothId) {
        Map<String, Object> latest = designMapper.findDesign(boothId, null);
        if (latest == null) {
            return 1;
        }
        Object v = latest.get("version");
        return v instanceof Number n ? n.intValue() + 1 : 1;
    }

    private DesignPlanDto toDto(Map<String, Object> row) {
        DesignSpec spec = readSpec(str(row.get("specJson")));
        Object v = row.get("version");
        return new DesignPlanDto(str(row.get("designId")), str(row.get("boothId")),
                v instanceof Number n ? n.intValue() : 0, str(row.get("status")),
                spec, str(row.get("updatedAt")));
    }

    private String writeSpec(DesignSpec spec) {
        try {
            return objectMapper.writeValueAsString(spec);
        } catch (JsonProcessingException e) {
            throw new ApiException(ErrorCode.VALIDATION, "설계 스펙을 직렬화할 수 없습니다.");
        }
    }

    private DesignSpec readSpec(String json) {
        if (json == null || json.isBlank()) {
            return null;
        }
        try {
            return objectMapper.readValue(json, DesignSpec.class);
        } catch (JsonProcessingException e) {
            return null;
        }
    }

    private static String str(Object o) {
        return o == null ? null : String.valueOf(o);
    }

    @Override
    public ComplianceReport precheck(DesignSpec spec) {
        Map<String, Object> metrics = new HashMap<>();
        if (spec.wallHeightM() != null) {
            metrics.put("booth.height_m", spec.wallHeightM());
        }
        if (spec.rigging() != null && spec.rigging().use() && spec.rigging().heightM() != null) {
            metrics.put("rigging.height_m", spec.rigging().heightM());
        }
        if (spec.mezzanineAreaRatio() != null) {
            metrics.put("mezzanine.area_ratio", spec.mezzanineAreaRatio());
        }
        if (spec.materials() != null && !spec.materials().isEmpty()) {
            boolean allFr = spec.materials().stream().allMatch(DesignSpec.Material::fireRetardant);
            metrics.put("materials.all_fire_retardant", allFr);
        }
        if (spec.usesDesignatedLightingOnly() != null) {
            metrics.put("lighting.uses_designated_only", spec.usesDesignatedLightingOnly());
        }
        return ruleEngine.evaluate("M3", metrics, null);
    }
}
