package com.erp.service;

import com.erp.common.BaseService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class DataflowDiagramService extends BaseService {

    @Autowired
    private CommonService commonService;
    @Autowired
    private ObjectMapper objectMapper;

    // ─────────────────────────────────────────────────────────
    // 목록 조회
    // ─────────────────────────────────────────────────────────

    /**
     * 관계도 목록 조회 (페이지네이션)
     * @return { diagrams: List, pagination: { page, size, total, totalPages } }
     */
    public Map<String, Object> getDataflowDiagrams(Map<String, Object> params) {
        int page = toInt(params.getOrDefault("page", 1), 1);
        int size = toInt(params.getOrDefault("size", 20), 20);

        params.put("page", page);
        params.put("size", size);
        params.put("limit", size);
        params.put("offset", (page - 1) * size);

        List<Map<String, Object>> diagrams = sqlSession.selectList("dataflowDiagram.selectList", params);
        diagrams.forEach(this::parseJsonbFields);
        Number totalNum = sqlSession.selectOne("dataflowDiagram.countList", params);
        int total = totalNum != null ? totalNum.intValue() : 0;
        int totalPages = (int) Math.ceil((double) total / size);

        Map<String, Object> pagination = new LinkedHashMap<>();
        pagination.put("page", page);
        pagination.put("size", size);
        pagination.put("total", total);
        pagination.put("total_pages", totalPages);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("diagrams", diagrams);
        result.put("pagination", pagination);
        return result;
    }

    // ─────────────────────────────────────────────────────────
    // 단건 조회
    // ─────────────────────────────────────────────────────────

    public Map<String, Object> getDataflowDiagramById(int diagramId, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("diagram_id", diagramId);
        params.put("company_code", companyCode);
        Map<String, Object> row = sqlSession.selectOne("dataflowDiagram.selectById", params);
        parseJsonbFields(row);
        return row;
    }

    // ─────────────────────────────────────────────────────────
    // 생성
    // ─────────────────────────────────────────────────────────

    @Transactional
    public Map<String, Object> createDataflowDiagram(Map<String, Object> body,
                                                      String companyCode,
                                                      String userId) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("created_by", userId);
        params.put("updated_by", userId);
        params.put("diagram_name",    body.get("diagram_name"));
        params.put("relationships",   toJsonString(body.get("relationships")));
        params.put("node_positions",  toJsonString(body.get("node_positions")));
        params.put("category",        toJsonString(body.get("category")));
        params.put("control",         toJsonString(body.get("control")));
        params.put("plan",            toJsonString(body.get("plan")));

        sqlSession.insert("dataflowDiagram.insert", params);  // useGeneratedKeys → diagram_id 주입

        return getDataflowDiagramById(
            toInt(params.get("diagram_id"), 0), companyCode);
    }

    // ─────────────────────────────────────────────────────────
    // 수정
    // ─────────────────────────────────────────────────────────

    @Transactional
    public Map<String, Object> updateDataflowDiagram(int diagramId,
                                                      Map<String, Object> body,
                                                      String companyCode,
                                                      String userId) {
        // 존재 여부 확인
        if (getDataflowDiagramById(diagramId, companyCode) == null) {
            return null;
        }

        Map<String, Object> params = new HashMap<>();
        params.put("diagram_id", diagramId);
        params.put("company_code", companyCode);
        params.put("updated_by", userId);

        // 단순 텍스트 필드 (null 이면 업데이트 안 함)
        if (body.containsKey("diagram_name") && body.get("diagram_name") != null) {
            params.put("diagram_name", body.get("diagram_name"));
        }
        // relationships: null 이면 업데이트 안 함 (필수 데이터)
        if (body.containsKey("relationships") && body.get("relationships") != null) {
            params.put("relationships", toJsonString(body.get("relationships")));
        }
        // nullable JSON 필드: containsKey 로 "명시적 전달" 여부 판단
        setNullableJsonField(params, body, "node_positions",  "update_node_positions");
        setNullableJsonField(params, body, "category",        "update_category");
        setNullableJsonField(params, body, "control",         "update_control");
        setNullableJsonField(params, body, "plan",            "update_plan");

        sqlSession.update("dataflowDiagram.update", params);

        return getDataflowDiagramById(diagramId, companyCode);
    }

    // ─────────────────────────────────────────────────────────
    // 삭제
    // ─────────────────────────────────────────────────────────

    @Transactional
    public boolean deleteDataflowDiagram(int diagramId, String companyCode) {
        if (getDataflowDiagramById(diagramId, companyCode) == null) {
            return false;
        }
        Map<String, Object> params = new HashMap<>();
        params.put("diagram_id", diagramId);
        params.put("company_code", companyCode);
        sqlSession.delete("dataflowDiagram.delete", params);
        return true;
    }

    // ─────────────────────────────────────────────────────────
    // 복제
    // ─────────────────────────────────────────────────────────

    @Transactional
    public Map<String, Object> copyDataflowDiagram(int diagramId,
                                                    String companyCode,
                                                    String newName,
                                                    String userId) {
        Map<String, Object> original = getDataflowDiagramById(diagramId, companyCode);
        if (original == null) {
            return null;
        }

        String copyName = (newName != null && !newName.isBlank())
                ? newName
                : generateCopyName(original, companyCode);

        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("created_by",  userId);
        params.put("updated_by",  userId);
        params.put("diagram_name",   copyName);
        params.put("relationships",  toRawString(original.get("relationships")));
        params.put("node_positions", toRawString(original.get("node_positions")));
        params.put("category",       toRawString(original.get("category")));
        // Node.js 원본과 동일하게 control/plan 은 복제에서 제외
        params.put("control", null);
        params.put("plan",    null);

        sqlSession.insert("dataflowDiagram.insert", params);

        return getDataflowDiagramById(
            toInt(params.get("diagram_id"), 0), companyCode);
    }

    // ─────────────────────────────────────────────────────────
    // private helpers
    // ─────────────────────────────────────────────────────────

    /**
     * 복제 이름 생성: "baseName (n+1)"
     * Node.js copyDataflowDiagram 동일 로직
     */
    private String generateCopyName(Map<String, Object> original, String companyCode) {
        String originalName = String.valueOf(original.get("diagram_name"));

        // 기존 "(n)" 접미사 제거해 baseName 추출
        Pattern suffixPattern = Pattern.compile("^(.+?)(\\s*\\((\\d+)\\))?$");
        Matcher m = suffixPattern.matcher(originalName);
        String baseName = m.matches() ? m.group(1) : originalName;

        // baseName 으로 시작하는 이름 목록 조회
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("pattern", baseName + "%");
        queryParams.put("company_code", companyCode);
        List<Map<String, Object>> existing = sqlSession.selectList("dataflowDiagram.selectNamesLike", queryParams);

        // 가장 큰 (n) 번호 탐색
        Pattern numPattern = Pattern.compile("\\((\\d+)\\)$");
        int maxNumber = existing.stream()
            .map(row -> String.valueOf(row.get("diagram_name")))
            .mapToInt(name -> {
                Matcher nm = numPattern.matcher(name);
                return nm.find() ? Integer.parseInt(nm.group(1)) : 0;
            })
            .max()
            .orElse(0);

        return baseName + " (" + (maxNumber + 1) + ")";
    }

    /**
     * nullable JSON 필드를 params 에 추가.
     * body 에 key 가 존재하면 flagKey=true 로 SET 포함 신호.
     */
    private void setNullableJsonField(Map<String, Object> params,
                                      Map<String, Object> body,
                                      String fieldKey,
                                      String flagKey) {
        if (body.containsKey(fieldKey)) {
            Object val = body.get(fieldKey);
            params.put(fieldKey, val != null ? toJsonString(val) : null);
            params.put(flagKey, Boolean.TRUE);
        }
    }

    /**
     * Object → JSON 문자열 (직렬화 필요한 경우만)
     * null → null
     * String → 그대로 반환 (이미 JSON 문자열)
     * Map/List → JSON 직렬화
     */
    private String toJsonString(Object obj) {
        if (obj == null) return null;
        if (obj instanceof String s) return s;
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.warn("JSON 직렬화 실패: {}", e.getMessage());
            return null;
        }
    }

    /**
     * DB에서 읽어온 JSONB 필드를 INSERT용 문자열로 변환.
     * PGobject 인 경우 value 필드 사용, String 이면 그대로, null 이면 null.
     * 이미 파싱된 Map/List 인 경우 재직렬화.
     */
    private String toRawString(Object obj) {
        if (obj == null) return null;
        if (obj instanceof String s) return s;
        if (obj instanceof Map || obj instanceof List) return toJsonString(obj);
        // PostgreSQL JDBC driver 가 PGobject 로 반환하는 경우
        // PGobject.toString() = value 필드 값 (JSON text)
        return obj.toString();
    }

    /**
     * DB에서 읽은 JSONB 필드(PGobject 또는 String)를 Java Object(Map/List)로 역직렬화.
     * Node.js pg 라이브러리가 자동으로 하는 처리를 Spring에서 수동으로 수행.
     */
    private static final List<String> JSONB_FIELDS = List.of(
            "relationships", "node_positions", "category", "control", "plan");

    private void parseJsonbFields(Map<String, Object> row) {
        if (row == null) return;
        for (String field : JSONB_FIELDS) {
            Object val = row.get(field);
            if (val == null) continue;
            String jsonStr = val instanceof String s ? s : val.toString();
            if (!jsonStr.isBlank()) {
                try {
                    row.put(field, objectMapper.readValue(jsonStr, Object.class));
                } catch (Exception ignored) {
                    // 파싱 실패 시 원문 유지
                }
            }
        }
    }

    /** Object → int (기본값 포함) */
    private int toInt(Object val, int defaultVal) {
        if (val == null) return defaultVal;
        try { return Integer.parseInt(val.toString()); }
        catch (NumberFormatException e) { return defaultVal; }
    }
}
