package com.zioinfo.kintex.module.m4;

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.common.geo.GeometryCodec;
import com.zioinfo.kintex.module.m4.dto.UtilityOrderDto;
import com.zioinfo.kintex.module.m4.dto.UtilityQuote;
import com.zioinfo.kintex.module.m4.dto.UtilityQuoteRequest;
import com.zioinfo.kintex.module.m4.dto.WiringRequest;
import com.zioinfo.kintex.module.m4.dto.WiringResult;
import com.zioinfo.kintex.module.m4.mapper.UtilityOrderMapper;
import com.zioinfo.kintex.module.m4.mapper.WiringMapper;
import com.zioinfo.kintex.rules.RateRuleSet;
import com.zioinfo.kintex.rules.RuleSetLoader;
import org.springframework.stereotype.Service;

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

/**
 * M4 서비스 — quote(요율 룰셋)·computeWiring(WiringMapper PostGIS)·신청 영속(UtilityOrderMapper) 배선 완료.
 * 위치표시도 PDF 실제 렌더는 문서 큐(M4-3, 후속)이며, 여기서는 신청을 영속하고 자산 URL·마감/릴레이 고지를 반환한다.
 */
@Service
public class UtilityServiceImpl implements UtilityService {

    /** 50A · 220V 단상 분전반 1식 근사 용량(kW) — 분전반 수량 산출 기준. */
    private static final double KW_PER_50A_BOX = 11.0;

    private final RuleSetLoader ruleSets;
    private final WiringMapper wiringMapper;
    private final UtilityOrderMapper orderMapper;
    private final ObjectMapper objectMapper;

    public UtilityServiceImpl(RuleSetLoader ruleSets, WiringMapper wiringMapper,
                              UtilityOrderMapper orderMapper, ObjectMapper objectMapper) {
        this.ruleSets = ruleSets;
        this.wiringMapper = wiringMapper;
        this.orderMapper = orderMapper;
        this.objectMapper = objectMapper;
    }

    @Override
    public UtilityQuote quote(UtilityQuoteRequest request) {
        RateRuleSet rates = ruleSets.rates();
        if (rates == null || rates.utility() == null) {
            throw ApiException.notImplemented("요율 룰셋 미로드");
        }
        Map<String, Object> util = rates.utility();
        long perKw = num(nested(util, "power", "perKw"));
        long box50A = num(nested(util, "power", "distributionBox50A"));
        long perLine = num(nested(util, "network", "internetWiredPerLine"));
        long perPlumb = num(nested(util, "plumbing", "perOutlet"));
        long perAir = num(nested(util, "compressedAir", "perOutlet"));

        double totalPowerKw = 0;
        if (request.devices() != null) {
            for (UtilityQuoteRequest.Device d : request.devices()) {
                totalPowerKw += d.count() * d.powerKw();
            }
        }
        int requestedKw = (int) Math.ceil(totalPowerKw - 1e-9);
        int boxes = requestedKw > 0 ? (int) Math.ceil(requestedKw / KW_PER_50A_BOX) : 0;

        List<UtilityQuote.QuoteLine> lines = new ArrayList<>();
        if (requestedKw > 0) {
            lines.add(new UtilityQuote.QuoteLine(
                    "전기 220V 단상 " + requestedKw + "kW", requestedKw, perKw, (long) requestedKw * perKw));
        }
        if (boxes > 0) {
            lines.add(new UtilityQuote.QuoteLine("분전반 50A 추가", boxes, box50A, (long) boxes * box50A));
        }
        if (request.networkWiredLines() > 0) {
            lines.add(new UtilityQuote.QuoteLine("인터넷 유선 회선", request.networkWiredLines(),
                    perLine, (long) request.networkWiredLines() * perLine));
        }
        if (request.plumbingOutlets() > 0) {
            lines.add(new UtilityQuote.QuoteLine("급배수 구", request.plumbingOutlets(),
                    perPlumb, (long) request.plumbingOutlets() * perPlumb));
        }
        if (request.compressedAirOutlets() > 0) {
            lines.add(new UtilityQuote.QuoteLine("압축공기 구", request.compressedAirOutlets(),
                    perAir, (long) request.compressedAirOutlets() * perAir));
        }
        long total = lines.stream().mapToLong(UtilityQuote.QuoteLine::amount).sum();

        return new UtilityQuote(round1(totalPowerKw), requestedKw, boxes, lines, total,
                rates.currency() == null ? "KRW" : rates.currency(),
                rates.rulesetVersion(),
                "공시가 기준이며 최종 금액은 킨텍스 확정 시 안내됩니다.");
    }

    @Override
    public WiringResult computeWiring(String eventId, String hallId, String boothId, WiringRequest request) {
        Boolean assumed = wiringMapper.isAssumedTrench(hallId);
        boolean assumedTrench = assumed == null || assumed;

        List<WiringResult.WiringPath> paths = new ArrayList<>();
        if (request.terminals() != null) {
            for (WiringRequest.Terminal t : request.terminals()) {
                if (t.position() == null || t.position().size() < 2) {
                    continue;
                }
                double fromX = t.position().get(0);
                double fromY = t.position().get(1);
                String kind = t.kind();
                Map<String, Object> path = wiringMapper.shortestPath(hallId, fromX, fromY, kind);
                if (path == null) {
                    continue; // 해당 kind 공급 트렌치 없음
                }
                List<List<Double>> coords = GeometryCodec.parseLineCoords(objectMapper, str(path.get("lineJson")));
                double lengthM = round2(dbl(path.get("lengthM")));
                paths.add(new WiringResult.WiringPath(kind, colorFor(kind), coords, lengthM, labelFor(t)));
            }
        }
        return new WiringResult(assumedTrench, paths);
    }

    @Override
    public UtilityOrderDto submitOrder(String eventId, String boothId, UtilityQuoteRequest request) {
        UtilityQuote quote = quote(request);
        String orderId = "uo-" + UUID.randomUUID();
        // 위치표시도 PDF 실제 렌더는 문서 큐(M4-3, 후속) — 자산 조회 경로만 안정적으로 제공.
        String locationDiagramUrl = "/api/events/" + eventId + "/booths/" + boothId
                + "/utility/location-diagram";
        String supplyTiming = "장치 마지막 날 오후";

        Map<String, Object> params = new java.util.LinkedHashMap<>();
        params.put("orderId", orderId);
        params.put("eventId", eventId);
        params.put("boothId", boothId);
        params.put("status", "submitted");
        params.put("quoteJson", writeQuote(quote));
        params.put("locationDiagramUrl", locationDiagramUrl);
        params.put("supplyTiming", supplyTiming);
        orderMapper.insertOrder(params);

        return new UtilityOrderDto(orderId, "submitted", quote, locationDiagramUrl, supplyTiming,
                "유틸리티 신청 마감 D-25 · 인터넷은 현장 추가신청 불가",
                "주최자 사무국/킨텍스에 제출 파일이 릴레이됩니다.");
    }

    @Override
    public UtilityOrderDto getOrder(String eventId, String boothId) {
        Map<String, Object> row = orderMapper.findOrder(eventId, boothId);
        if (row == null) {
            throw new ApiException(ErrorCode.NOT_FOUND, "제출된 유틸리티 신청이 없습니다.");
        }
        UtilityQuote quote = readQuote(str(row.get("quoteJson")));
        return new UtilityOrderDto(str(row.get("orderId")), str(row.get("status")), quote,
                str(row.get("locationDiagramUrl")), str(row.get("supplyTiming")),
                "유틸리티 신청 마감 D-25 · 인터넷은 현장 추가신청 불가",
                "주최자 사무국/킨텍스에 제출 파일이 릴레이됩니다.");
    }

    /** S6 색상 규약: power=red · network=blue · plumbing/water/air=green(design.md §1-2). */
    private static String colorFor(String kind) {
        if (kind == null) return "green";
        return switch (kind) {
            case "power" -> "red";
            case "network" -> "blue";
            default -> "green";
        };
    }

    private static String labelFor(WiringRequest.Terminal t) {
        if ("power".equals(t.kind()) && t.kw() != null) {
            return (t.kw() == Math.floor(t.kw()) ? String.valueOf(t.kw().intValue()) : String.valueOf(t.kw())) + "kW";
        }
        if ("network".equals(t.kind())) {
            return "유선";
        }
        return t.kind();
    }

    private String writeQuote(UtilityQuote quote) {
        try {
            return objectMapper.writeValueAsString(quote);
        } catch (JsonProcessingException e) {
            throw new ApiException(ErrorCode.INTERNAL, "견적 직렬화에 실패했습니다.");
        }
    }

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

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

    private static double dbl(Object o) {
        if (o instanceof Number n) return n.doubleValue();
        if (o == null) return 0.0;
        try {
            return Double.parseDouble(String.valueOf(o));
        } catch (NumberFormatException e) {
            return 0.0;
        }
    }

    private static double round2(double v) {
        return Math.round(v * 100.0) / 100.0;
    }

    // --- 요율 맵 탐색 유틸 ---
    @SuppressWarnings("unchecked")
    private static Object nested(Map<String, Object> map, String a, String b) {
        Object inner = map.get(a);
        if (inner instanceof Map<?, ?> m) {
            return ((Map<String, Object>) m).get(b);
        }
        return null;
    }

    private static long num(Object o) {
        if (o instanceof Number n) return n.longValue();
        if (o == null) return 0L;
        try {
            return Long.parseLong(String.valueOf(o));
        } catch (NumberFormatException e) {
            return 0L;
        }
    }

    private static double round1(double v) {
        return Math.round(v * 10.0) / 10.0;
    }
}
