package com.erp.service;

import com.erp.common.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * Common Code Service — 마스터-디테일 패턴.
 *
 *  • code_info   : 1레벨 그룹 마스터 (PK = code_info + company_code)
 *  • code_detail : 2레벨 ~ 무한대 트리 (PK = code_detail_id, parent_detail_id 로 self-ref)
 *
 * 옛 캐스케이딩/카테고리 구조 폐기. 단일 그룹 안에서 재귀 트리.
 */
@Service
@Slf4j
public class CommonCodeService extends BaseService {

    private static final String NS = "commonCode.";

    // ══════════════════════════════════════════════════════════════
    // CODE_INFO — 그룹 마스터 CRUD
    // ══════════════════════════════════════════════════════════════

    public Map<String, Object> getCodeInfoList(Map<String, Object> params) {
        int page = toInt(params.get("page"), 1);
        int size = toInt(params.get("size"), 20);
        params.put("limit",  size);
        params.put("offset", (page - 1) * size);

        Object isActiveRaw = params.get("is_active");
        if (isActiveRaw != null) params.put("is_active", toActiveStr(isActiveRaw));

        List<Map<String, Object>> data = sqlSession.selectList(NS + "getCodeInfoList", params);
        Integer totalObj = sqlSession.selectOne(NS + "getCodeInfoListCnt", params);
        int total = totalObj != null ? totalObj : 0;

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

    public Map<String, Object> getCodeInfoInfo(String codeInfo, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_info",    codeInfo);
        params.put("company_code", companyCode);
        return sqlSession.selectOne(NS + "getCodeInfoInfo", params);
    }

    @Transactional
    public Map<String, Object> insertCodeInfo(Map<String, Object> body, String companyCode, String userId) {
        Object rawCodeInfo = body.get("code_info");
        String codeInfo = rawCodeInfo == null ? null : rawCodeInfo.toString().trim();
        if (codeInfo != null && !codeInfo.isEmpty()
                && getCodeInfoInfo(codeInfo, companyCode) != null) {
            throw new IllegalArgumentException("이미 존재하는 그룹 코드입니다: " + codeInfo);
        }

        Map<String, Object> params = new HashMap<>();
        params.put("code_info",     body.get("code_info"));
        params.put("code_name",     body.get("code_name"));
        params.put("code_name_eng", body.getOrDefault("code_name_eng", null));
        params.put("description",   body.getOrDefault("description",   null));
        params.put("sort_order",    body.getOrDefault("sort_order",    0));
        params.put("is_active",     toActiveStr(body.getOrDefault("is_active", true)));
        params.put("menu_objid",    body.getOrDefault("menu_objid",    null));
        params.put("company_code",  companyCode);
        params.put("created_by",    userId);
        params.put("updated_by",    userId);

        sqlSession.insert(NS + "insertCodeInfo", params);

        return getCodeInfoInfo(String.valueOf(params.get("code_info")), companyCode);
    }

    @Transactional
    public Map<String, Object> updateCodeInfo(String codeInfo, Map<String, Object> body,
                                              String companyCode, String userId) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_info",    codeInfo);
        params.put("company_code", companyCode);
        params.put("updated_by",   userId);

        if (body.containsKey("code_name"))     params.put("code_name",     body.get("code_name"));
        if (body.containsKey("code_name_eng")) params.put("code_name_eng", body.get("code_name_eng"));
        if (body.containsKey("description"))   params.put("description",   body.get("description"));
        if (body.containsKey("sort_order"))    params.put("sort_order",    body.get("sort_order"));
        if (body.containsKey("is_active"))     params.put("is_active",     toActiveStr(body.get("is_active")));
        if (body.containsKey("menu_objid"))    params.put("menu_objid",    body.get("menu_objid"));

        int updated = sqlSession.update(NS + "updateCodeInfo", params);
        if (updated == 0) return null;

        return getCodeInfoInfo(codeInfo, companyCode);
    }

    @Transactional
    public void deleteCodeInfo(String codeInfo, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_info",    codeInfo);
        params.put("company_code", companyCode);

        int deleted = sqlSession.delete(NS + "deleteCodeInfo", params);
        if (deleted == 0) throw new IllegalArgumentException("코드 그룹을 찾을 수 없습니다.");
    }

    public Map<String, Object> checkCodeInfoDuplicate(String field, String value,
                                                      String excludeCode, String companyCode) {
        Map<String, Object> result = new LinkedHashMap<>();
        if (value == null || value.trim().isEmpty()) {
            result.put("is_duplicate", false);
            result.put("message",      "값을 입력해주세요.");
            return result;
        }

        Map<String, Object> params = new HashMap<>();
        params.put("field",        field != null ? field : "code_info");
        params.put("value",        value.trim());
        params.put("exclude_code", excludeCode);
        params.put("company_code", companyCode);

        Integer countObj = sqlSession.selectOne(NS + "getCodeInfoDuplicateByField", params);
        boolean isDuplicate = countObj != null && countObj > 0;

        result.put("is_duplicate", isDuplicate);
        result.put("message", isDuplicate ? "이미 사용 중인 값입니다." : "사용 가능한 값입니다.");
        return result;
    }

    // ══════════════════════════════════════════════════════════════
    // CODE_DETAIL — 디테일 트리 CRUD
    // ══════════════════════════════════════════════════════════════

    public Map<String, Object> getCodeDetailList(String codeInfo, Map<String, Object> params) {
        int page = toInt(params.get("page"), 1);
        int size = toInt(params.get("size"), 20);
        params.put("code_info", codeInfo);
        params.put("limit",     size);
        params.put("offset",    (page - 1) * size);

        Object isActiveRaw = params.get("is_active");
        if (isActiveRaw != null) params.put("is_active", toActiveStr(isActiveRaw));

        Object parentRaw = params.get("parent_detail_id");
        if (parentRaw != null && !parentRaw.toString().isEmpty()) {
            params.put("parent_detail_id", toLong(parentRaw));
        } else {
            params.remove("parent_detail_id");
        }

        List<Map<String, Object>> data = sqlSession.selectList(NS + "getCodeDetailList", params);
        Integer totalObj = sqlSession.selectOne(NS + "getCodeDetailListCnt", params);
        int total = totalObj != null ? totalObj : 0;

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

    /**
     * 그룹 전체 트리 — 평탄화된 리스트로 반환 (depth + sort_order 순).
     * 프론트가 parent_detail_id 로 nest 처리하기 좋게.
     */
    public List<Map<String, Object>> getCodeDetailTree(String codeInfo, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_info",    codeInfo);
        params.put("company_code", companyCode);
        return sqlSession.selectList(NS + "getCodeDetailTree", params);
    }

    public Map<String, Object> getCodeDetailInfo(Long codeDetailId, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_detail_id", codeDetailId);
        params.put("company_code",   companyCode);
        return sqlSession.selectOne(NS + "getCodeDetailInfo", params);
    }

    @Transactional
    public Map<String, Object> insertCodeDetail(String codeInfo, Map<String, Object> body,
                                                String companyCode, String userId) {
        // parent_detail_id 기반 depth 자동 계산. NULL = 그룹 직속 (depth=2).
        Long parentDetailId = toLong(body.getOrDefault("parent_detail_id", null));
        int depth = 2;
        if (parentDetailId != null) {
            Map<String, Object> parentParams = new HashMap<>();
            parentParams.put("code_detail_id", parentDetailId);
            parentParams.put("company_code",   companyCode);
            Integer parentDepth = sqlSession.selectOne(NS + "getCodeDetailParentDepth", parentParams);
            depth = (parentDepth != null ? parentDepth : 1) + 1;
        }

        Map<String, Object> params = new HashMap<>();
        params.put("code_info",        codeInfo);
        params.put("parent_detail_id", parentDetailId);
        params.put("code_value",       body.get("code_value"));
        params.put("code_name",        body.get("code_name"));
        params.put("code_name_eng",    body.getOrDefault("code_name_eng", null));
        params.put("description",      body.getOrDefault("description",   null));
        params.put("depth",            depth);
        params.put("sort_order",       body.getOrDefault("sort_order",    0));
        params.put("is_active",        toActiveStr(body.getOrDefault("is_active", true)));
        params.put("company_code",     companyCode);
        params.put("created_by",       userId);
        params.put("updated_by",       userId);

        sqlSession.insert(NS + "insertCodeDetail", params);

        Object newIdRaw = params.get("code_detail_id");
        Long newId = toLong(newIdRaw);
        return newId != null ? getCodeDetailInfo(newId, companyCode) : null;
    }

    @Transactional
    public Map<String, Object> updateCodeDetail(Long codeDetailId, Map<String, Object> body,
                                                String companyCode, String userId) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_detail_id", codeDetailId);
        params.put("company_code",   companyCode);
        params.put("updated_by",     userId);

        if (body.containsKey("code_value"))     params.put("code_value",     body.get("code_value"));
        if (body.containsKey("code_name"))      params.put("code_name",      body.get("code_name"));
        if (body.containsKey("code_name_eng"))  params.put("code_name_eng",  body.get("code_name_eng"));
        if (body.containsKey("description"))    params.put("description",    body.get("description"));
        if (body.containsKey("sort_order"))     params.put("sort_order",     body.get("sort_order"));
        if (body.containsKey("is_active"))      params.put("is_active",      toActiveStr(body.get("is_active")));

        // parent_detail_id 변경 시 depth 재계산.
        if (body.containsKey("parent_detail_id")) {
            Long newParent = toLong(body.get("parent_detail_id"));
            int newDepth = 2;
            if (newParent != null) {
                Map<String, Object> parentParams = new HashMap<>();
                parentParams.put("code_detail_id", newParent);
                parentParams.put("company_code",   companyCode);
                Integer parentDepth = sqlSession.selectOne(NS + "getCodeDetailParentDepth", parentParams);
                newDepth = (parentDepth != null ? parentDepth : 1) + 1;
            }
            params.put("reparent",         true);
            params.put("parent_detail_id", newParent);
            params.put("depth",            newDepth);
        }

        int updated = sqlSession.update(NS + "updateCodeDetail", params);
        if (updated == 0) return null;

        return getCodeDetailInfo(codeDetailId, companyCode);
    }

    @Transactional
    public void deleteCodeDetail(Long codeDetailId, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_detail_id", codeDetailId);
        params.put("company_code",   companyCode);

        int deleted = sqlSession.delete(NS + "deleteCodeDetail", params);
        if (deleted == 0) throw new IllegalArgumentException("코드를 찾을 수 없습니다.");
    }

    public Map<String, Object> checkCodeDetailDuplicate(String codeInfo, String codeValue,
                                                        Long excludeId, String companyCode) {
        Map<String, Object> result = new LinkedHashMap<>();
        if (codeValue == null || codeValue.trim().isEmpty()) {
            result.put("is_duplicate", false);
            result.put("message",      "값을 입력해주세요.");
            return result;
        }

        Map<String, Object> params = new HashMap<>();
        params.put("code_info",    codeInfo);
        params.put("code_value",   codeValue.trim());
        params.put("exclude_id",   excludeId);
        params.put("company_code", companyCode);

        Integer countObj = sqlSession.selectOne(NS + "getCodeDetailDuplicateCnt", params);
        boolean isDuplicate = countObj != null && countObj > 0;

        result.put("is_duplicate", isDuplicate);
        result.put("message", isDuplicate ? "이미 사용 중인 값입니다." : "사용 가능한 값입니다.");
        return result;
    }

    public Map<String, Object> hasCodeDetailChildren(Long codeDetailId, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("code_detail_id", codeDetailId);
        params.put("company_code",   companyCode);

        Integer countObj = sqlSession.selectOne(NS + "getCodeDetailChildrenCnt", params);
        int count = countObj != null ? countObj : 0;

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("has_children", count > 0);
        result.put("count",        count);
        return result;
    }

    // ══════════════════════════════════════════════════════════════
    // Private helpers
    // ══════════════════════════════════════════════════════════════

    /** boolean / String / Number → VARCHAR(1) 'Y'/'N'. */
    private String toActiveStr(Object val) {
        if (val == null) return "Y";
        if (val instanceof Boolean b) return b ? "Y" : "N";
        if (val instanceof Number n)  return n.intValue() != 0 ? "Y" : "N";
        String s = val.toString().toLowerCase();
        return ("true".equals(s) || "y".equals(s) || "1".equals(s)) ? "Y" : "N";
    }

    private int toInt(Object val, int defaultVal) {
        if (val == null) return defaultVal;
        try { return Integer.parseInt(val.toString()); }
        catch (NumberFormatException e) { return defaultVal; }
    }

    private Long toLong(Object val) {
        if (val == null) return null;
        if (val instanceof Number n) return n.longValue();
        String s = val.toString().trim();
        if (s.isEmpty() || "null".equalsIgnoreCase(s)) return null;
        try { return Long.parseLong(s); }
        catch (NumberFormatException e) { return null; }
    }
}
