package com.erp.service;

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

import java.util.*;

/**
 * Component Standard 서비스
 *
 * Node.js componentStandardService.ts 포팅.
 * component_standards 테이블 CRUD + 카테고리/통계/정렬/복제.
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class ComponentStandardService extends BaseService {

    private static final String NS = "componentStandard.";

    private final CommonService commonService;
    private final ObjectMapper objectMapper;

    /** 허용된 정렬 컬럼 (SQL 인젝션 방지) */
    private static final Set<String> VALID_SORT_COLUMNS = Set.of(
            "sort_order", "component_name", "category", "created_date", "updated_date");

    // ══════════════════════════════════════════════════════════
    // 목록 조회
    // ══════════════════════════════════════════════════════════

    /**
     * 컴포넌트 목록 조회 (필터, 정렬, 페이지네이션 포함)
     *
     * @return { components, total, limit, offset }
     */
    public Map<String, Object> getComponents(Map<String, Object> params) {
        // 기본값: active = 'Y'
        if (params.get("active") == null) params.put("active", "Y");

        // 정렬 컬럼 화이트리스트 검증
        String sort = str(params.getOrDefault("sort", "sort_order"));
        params.put("sort_column", VALID_SORT_COLUMNS.contains(sort) ? sort : "sort_order");
        params.put("sort_order",  "desc".equalsIgnoreCase(str(params.get("order"))) ? "DESC" : "ASC");

        // 검색 패턴
        if (params.get("search") != null) {
            params.put("search_pattern", "%" + params.get("search") + "%");
        }

        // 페이지네이션
        commonService.applyPagination(params);

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

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("components", components);
        result.put("total",      total);
        result.put("limit",      params.get("limit"));
        result.put("offset",     params.getOrDefault("offset", 0));
        return result;
    }

    // ══════════════════════════════════════════════════════════
    // 단건 조회
    // ══════════════════════════════════════════════════════════

    public Map<String, Object> getComponent(String componentCode) {
        Map<String, Object> params = Map.of("component_code", componentCode);
        Map<String, Object> component = sqlSession.selectOne(NS + "selectComponent", params);
        if (component == null) {
            throw new RuntimeException("컴포넌트를 찾을 수 없습니다: " + componentCode);
        }
        return component;
    }

    // ══════════════════════════════════════════════════════════
    // 생성
    // ══════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> createComponent(Map<String, Object> params) {
        // 중복 코드 확인
        if (sqlSession.selectOne(NS + "checkDuplicate", params) != null) {
            throw new RuntimeException("이미 존재하는 컴포넌트 코드입니다: " + params.get("component_code"));
        }

        // 'active' → 'is_active' 변환
        if (params.containsKey("active")) {
            params.put("is_active", params.remove("active"));
        }

        // 기본값 설정
        params.putIfAbsent("is_active", "Y");
        params.putIfAbsent("is_public", "N");
        params.putIfAbsent("sort_order", 0);

        // JSONB 필드 직렬화
        serializeJsonFields(params);

        sqlSession.insert(NS + "insertComponent", params);
        return sqlSession.selectOne(NS + "selectComponent", Map.of("component_code", params.get("component_code")));
    }

    // ══════════════════════════════════════════════════════════
    // 수정
    // ══════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> updateComponent(String componentCode, Map<String, Object> data) {
        // 존재 확인
        getComponent(componentCode);

        // 'active' → 'is_active' 변환
        if (data.containsKey("active")) {
            data.put("is_active", data.remove("active"));
        }

        data.put("component_code", componentCode);
        serializeJsonFields(data);

        sqlSession.update(NS + "updateComponent", data);
        return sqlSession.selectOne(NS + "selectComponent", Map.of("component_code", componentCode));
    }

    // ══════════════════════════════════════════════════════════
    // 삭제
    // ══════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> deleteComponent(String componentCode) {
        getComponent(componentCode); // 존재 확인
        sqlSession.delete(NS + "deleteComponent", Map.of("component_code", componentCode));
        return Map.of("message", "컴포넌트가 삭제되었습니다: " + componentCode);
    }

    // ══════════════════════════════════════════════════════════
    // 정렬 순서 업데이트 (배치)
    // ══════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> updateSortOrder(List<Map<String, Object>> updates) {
        for (Map<String, Object> item : updates) {
            sqlSession.update(NS + "updateSortOrder", item);
        }
        return Map.of("message", "정렬 순서가 업데이트되었습니다.");
    }

    // ══════════════════════════════════════════════════════════
    // 복제
    // ══════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> duplicateComponent(String sourceCode, String newCode, String newName) {
        Map<String, Object> source = getComponent(sourceCode);

        // 새 코드 중복 확인
        Map<String, Object> dupCheck = new HashMap<>();
        dupCheck.put("component_code", newCode);
        if (sqlSession.selectOne(NS + "checkDuplicate", dupCheck) != null) {
            throw new RuntimeException("이미 존재하는 컴포넌트 코드입니다: " + newCode);
        }

        Map<String, Object> newComponent = new HashMap<>(source);
        newComponent.put("component_code", newCode);
        newComponent.put("component_name", newName);

        serializeJsonFields(newComponent);
        sqlSession.insert(NS + "insertComponent", newComponent);
        return sqlSession.selectOne(NS + "selectComponent", Map.of("component_code", newCode));
    }

    // ══════════════════════════════════════════════════════════
    // 카테고리 목록
    // ══════════════════════════════════════════════════════════

    public List<String> getCategories(Map<String, Object> params) {
        List<Map<String, Object>> rows = sqlSession.selectList(NS + "selectCategories", params);
        List<String> categories = new ArrayList<>();
        for (Map<String, Object> row : rows) {
            Object cat = row.get("category");
            if (cat != null) categories.add(cat.toString());
        }
        return categories;
    }

    // ══════════════════════════════════════════════════════════
    // 통계
    // ══════════════════════════════════════════════════════════

    public Map<String, Object> getStatistics(Map<String, Object> params) {
        Integer totalObj = sqlSession.selectOne(NS + "countStatisticsTotal", params);
        int total = totalObj != null ? totalObj : 0;

        List<Map<String, Object>> byCategoryRaw = sqlSession.selectList(NS + "selectStatisticsByCategory", params);
        List<Map<String, Object>> byCategory = new ArrayList<>();
        for (Map<String, Object> row : byCategoryRaw) {
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("category", row.get("category"));
            item.put("count",    toLong(row.get("count")));
            byCategory.add(item);
        }

        List<Map<String, Object>> byStatusRaw = sqlSession.selectList(NS + "selectStatisticsByStatus");
        List<Map<String, Object>> byStatus = new ArrayList<>();
        for (Map<String, Object> row : byStatusRaw) {
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("status", row.get("is_active"));
            item.put("count",  toLong(row.get("count")));
            byStatus.add(item);
        }

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("total",      total);
        result.put("by_category", byCategory);
        result.put("by_status",   byStatus);
        return result;
    }

    // ══════════════════════════════════════════════════════════
    // 중복 코드 체크
    // ══════════════════════════════════════════════════════════

    public boolean checkDuplicate(Map<String, Object> params) {
        return sqlSession.selectOne(NS + "checkDuplicate", params) != null;
    }

    // ══ private helpers ═══════════════════════════════════════

    /** component_config, default_size 값이 Map/List이면 JSON 문자열로 직렬화 */
    private void serializeJsonFields(Map<String, Object> params) {
        for (String field : new String[]{"component_config", "default_size"}) {
            Object val = params.get(field);
            if (val != null && !(val instanceof String)) {
                try {
                    params.put(field, objectMapper.writeValueAsString(val));
                } catch (JsonProcessingException e) {
                    log.warn("JSON 직렬화 실패 ({}): {}", field, e.getMessage());
                }
            }
        }
    }

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

    private long toLong(Object o) {
        if (o == null) return 0L;
        if (o instanceof Number n) return n.longValue();
        return Long.parseLong(o.toString());
    }
}
