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.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 DataflowService extends BaseService {

    @Autowired
    private ObjectMapper objectMapper;

    /** 테이블명/컬럼명 안전성 검증 패턴 */
    private static final Pattern SAFE_IDENTIFIER = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*$");

    // ═══════════════════════════════════════════════════════════════════
    // table_relationships CRUD
    // ═══════════════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> insertTableRelationship(Map<String, Object> body,
                                                       String companyCode,
                                                       String userId) {
        String relationshipName = str(body.get("relationship_name"));
        String fromTableName    = str(body.get("from_table_name"));
        String fromColumnName   = str(body.get("from_column_name"));
        String toTableName      = str(body.get("to_table_name"));
        String toColumnName     = str(body.get("to_column_name"));
        String relationshipType = body.getOrDefault("relationship_type", "one-to-one").toString();
        String connectionType   = body.getOrDefault("connection_type", "simple-key").toString();

        // diagram_id 결정: 전달된 값 or max+1
        int diagramId;
        Object diagramIdObj = body.get("diagram_id");
        if (diagramIdObj != null) {
            diagramId = toInt(diagramIdObj);
        } else {
            Map<String, Object> maxParams = new HashMap<>();
            maxParams.put("company_code", companyCode);
            Map<String, Object> maxRow = sqlSession.selectOne("dataflow.getMaxDiagramId", maxParams);
            diagramId = toInt(maxRow != null ? maxRow.get("max_diagram_id") : null) + 1;
        }

        // 중복 확인
        Map<String, Object> checkParams = new HashMap<>();
        checkParams.put("diagram_id",       diagramId);
        checkParams.put("from_table_name",   fromTableName);
        checkParams.put("from_column_name",  fromColumnName);
        checkParams.put("to_table_name",     toTableName);
        checkParams.put("to_column_name",    toColumnName);
        checkParams.put("company_code",     companyCode);
        if (sqlSession.selectOne("dataflow.getExistingRelationshipInfo", checkParams) != null) {
            throw new IllegalArgumentException(
                "이미 존재하는 관계입니다: " + fromTableName + "." + fromColumnName
                + " → " + toTableName + "." + toColumnName);
        }

        Map<String, Object> params = new HashMap<>();
        params.put("diagram_id",        diagramId);
        params.put("relationship_name", relationshipName);
        params.put("from_table_name",    fromTableName);
        params.put("from_column_name",   fromColumnName);
        params.put("to_table_name",      toTableName);
        params.put("to_column_name",     toColumnName);
        params.put("relationship_type", relationshipType);
        params.put("connection_type",   connectionType);
        params.put("company_code",      companyCode);
        params.put("settings",         toJsonString(body.get("settings")));
        params.put("created_by",        userId);

        sqlSession.insert("dataflow.insertTableRelationship", params);

        Map<String, Object> idParams = new HashMap<>();
        idParams.put("relationship_id", params.get("relationship_id"));
        idParams.put("company_code",    companyCode);
        return sqlSession.selectOne("dataflow.getTableRelationshipInfo", idParams);
    }

    public List<Map<String, Object>> getTableRelationshipList(String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        return sqlSession.selectList("dataflow.getTableRelationshipList", params);
    }

    public Map<String, Object> getTableRelationshipInfo(int relationshipId, String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("relationship_id", relationshipId);
        params.put("company_code",    companyCode);
        return sqlSession.selectOne("dataflow.getTableRelationshipInfo", params);
    }

    @Transactional
    public Map<String, Object> updateTableRelationship(int relationshipId,
                                                       Map<String, Object> updateData,
                                                       String companyCode,
                                                       String userId) {
        if (getTableRelationshipInfo(relationshipId, companyCode) == null) return null;

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

        copyIfPresent(updateData, params, "relationship_name");
        copyIfPresent(updateData, params, "from_table_name");
        copyIfPresent(updateData, params, "from_column_name");
        copyIfPresent(updateData, params, "to_table_name");
        copyIfPresent(updateData, params, "to_column_name");
        copyIfPresent(updateData, params, "relationship_type");
        copyIfPresent(updateData, params, "connection_type");
        if (updateData.containsKey("settings")) {
            params.put("settings", toJsonString(updateData.get("settings")));
        }

        sqlSession.update("dataflow.updateTableRelationship", params);
        return getTableRelationshipInfo(relationshipId, companyCode);
    }

    @Transactional
    public boolean deleteTableRelationship(int relationshipId, String companyCode) {
        if (getTableRelationshipInfo(relationshipId, companyCode) == null) return false;

        Map<String, Object> params = new HashMap<>();
        params.put("relationship_id", relationshipId);
        params.put("company_code",    companyCode);
        sqlSession.update("dataflow.softDeleteTableRelationship", params);
        return true;
    }

    // ═══════════════════════════════════════════════════════════════════
    // data_relationship_bridge
    // ═══════════════════════════════════════════════════════════════════

    @Transactional
    public Map<String, Object> insertDataLink(Map<String, Object> body,
                                              String companyCode,
                                              String userId) {
        Map<String, Object> params = new HashMap<>();
        params.put("relationship_id", toInt(body.get("relationship_id")));
        params.put("from_table_name",  body.get("from_table_name"));
        params.put("from_column_name", body.get("from_column_name"));
        params.put("to_table_name",    body.get("to_table_name"));
        params.put("to_column_name",   body.get("to_column_name"));
        params.put("connection_type", body.get("connection_type"));
        params.put("company_code",    companyCode);
        params.put("bridge_data",     toJsonString(body.get("bridge_data")));
        params.put("created_by",      userId);

        sqlSession.insert("dataflow.insertDataLink", params);

        // useGeneratedKeys 로 bridgeId 자동 채워짐
        Map<String, Object> result = new LinkedHashMap<>(params);
        result.put("bridge_id", params.get("bridge_id"));
        return result;
    }

    public List<Map<String, Object>> getDataLinkList(int relationshipId,
                                                     String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("relationship_id", relationshipId);
        params.put("company_code",    companyCode);
        return sqlSession.selectList("dataflow.getDataLinkList", params);
    }

    @Transactional
    public void deleteDataLink(int bridgeId, String companyCode, String deletedBy) {
        Map<String, Object> params = new HashMap<>();
        params.put("bridge_id",    bridgeId);
        params.put("company_code", companyCode);
        sqlSession.update("dataflow.softDeleteDataLink", params);
    }

    // ═══════════════════════════════════════════════════════════════════
    // dynamic table data
    // ═══════════════════════════════════════════════════════════════════

    public Map<String, Object> getTableDataList(String tableName,
                                            int page, int limit,
                                            String search, String searchColumn,
                                            String companyCode) {
        // 안전성 검증 (SQL 인젝션 방지)
        if (!SAFE_IDENTIFIER.matcher(tableName).matches()) {
            throw new IllegalArgumentException("유효하지 않은 테이블명입니다.");
        }
        if (searchColumn != null && !searchColumn.isEmpty()
                && !SAFE_IDENTIFIER.matcher(searchColumn).matches()) {
            throw new IllegalArgumentException("유효하지 않은 컬럼명입니다.");
        }

        // 테이블 존재 확인
        Map<String, Object> checkParams = new HashMap<>();
        checkParams.put("table_name", tableName.toLowerCase());
        List<Map<String, Object>> tableInfo = sqlSession.selectList("dataflow.checkTableExists", checkParams);
        if (tableInfo == null || tableInfo.isEmpty()) {
            throw new IllegalArgumentException("테이블 '" + tableName + "'이 존재하지 않습니다.");
        }

        Map<String, Object> params = new HashMap<>();
        params.put("table_name", tableName);
        params.put("limit",     limit);
        params.put("offset",    (page - 1) * limit);
        if (search != null && !search.isEmpty()
                && searchColumn != null && !searchColumn.isEmpty()) {
            params.put("search",       search);
            params.put("search_column", searchColumn);
        }

        Number totalNum = sqlSession.selectOne("dataflow.countTableData", params);
        int total = totalNum != null ? totalNum.intValue() : 0;
        List<Map<String, Object>> data = sqlSession.selectList("dataflow.getTableDataList", params);
        int totalPages                 = (int) Math.ceil((double) total / limit);

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

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

    // ═══════════════════════════════════════════════════════════════════
    // diagram operations
    // ═══════════════════════════════════════════════════════════════════

    public Map<String, Object> getDiagramList(String companyCode,
                                              int page, int size,
                                              String searchTerm) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        if (searchTerm != null && !searchTerm.isEmpty()) {
            params.put("search_term", searchTerm);
        }

        List<Map<String, Object>> rows = sqlSession.selectList("dataflow.getRelationshipForDiagramList", params);

        // diagram_id 기준으로 그룹화
        Map<Integer, Map<String, Object>> diagramMap = new LinkedHashMap<>();
        Map<Integer, Set<String>>         tableSetMap = new HashMap<>();

        for (Map<String, Object> rel : rows) {
            int diagramId = toInt(rel.get("diagram_id"));
            if (diagramId == 0) continue;

            if (!diagramMap.containsKey(diagramId)) {
                Map<String, Object> d = new LinkedHashMap<>();
                d.put("diagram_id",         diagramId);
                d.put("diagram_name",       rel.get("relationship_name"));
                d.put("connection_type",    rel.get("connection_type"));
                d.put("relationship_type",  rel.get("relationship_type"));
                d.put("relationship_count", 0);
                d.put("created_at",         rel.get("created_date"));
                d.put("created_by",         rel.get("created_by"));
                d.put("updated_at",         rel.get("updated_date"));
                d.put("updated_by",         rel.get("updated_by"));
                diagramMap.put(diagramId, d);
                tableSetMap.put(diagramId, new LinkedHashSet<>());
            }

            Map<String, Object> d     = diagramMap.get(diagramId);
            Set<String>         tables = tableSetMap.get(diagramId);

            d.put("relationship_count", toInt(d.get("relationship_count")) + 1);
            if (rel.get("from_table_name") != null) tables.add(rel.get("from_table_name").toString());
            if (rel.get("to_table_name")   != null) tables.add(rel.get("to_table_name").toString());
        }

        // tableCount / tables 설정
        diagramMap.forEach((id, d) -> {
            Set<String> tables = tableSetMap.get(id);
            d.put("table_count", tables.size());
            d.put("tables",     new ArrayList<>(tables));
        });

        List<Map<String, Object>> allDiagrams = new ArrayList<>(diagramMap.values());
        int total  = allDiagrams.size();
        int offset = (page - 1) * size;
        int end    = Math.min(offset + size, total);
        List<Map<String, Object>> pageData = (offset < total)
                ? allDiagrams.subList(offset, end)
                : new ArrayList<>();

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("diagrams",   pageData);
        result.put("total",      total);
        result.put("page",       page);
        result.put("size",       size);
        result.put("total_pages", (int) Math.ceil((double) total / size));
        return result;
    }

    public List<Map<String, Object>> getDiagramRelationshipList(String companyCode,
                                                                String diagramName) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code",  companyCode);
        params.put("diagram_name",  diagramName);
        return sqlSession.selectList("dataflow.getDiagramRelationshipListByName", params);
    }

    public List<Map<String, Object>> getDiagramRelationshipListByDiagramId(String companyCode,
                                                                           int diagramId) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("diagram_id",   diagramId);
        return sqlSession.selectList("dataflow.getDiagramRelationshipListByDiagramId", params);
    }

    public List<Map<String, Object>> getDiagramRelationshipListByRelationshipId(String companyCode,
                                                                                int relationshipId) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code",    companyCode);
        params.put("relationship_id", relationshipId);
        return sqlSession.selectList("dataflow.getDiagramRelationshipListByRelationshipId", params);
    }

    @Transactional
    public String copyDiagram(String companyCode, String diagramName) {
        // 원본 조회
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("company_code", companyCode);
        queryParams.put("diagram_name", diagramName);
        List<Map<String, Object>> existing = sqlSession.selectList("dataflow.getDiagramRelationshipListByName", queryParams);

        if (existing == null || existing.isEmpty()) {
            throw new IllegalArgumentException("복사할 관계도를 찾을 수 없습니다.");
        }

        // 새 diagram_id 생성
        Map<String, Object> maxParams = new HashMap<>();
        maxParams.put("company_code", companyCode);
        Map<String, Object> maxRow    = sqlSession.selectOne("dataflow.getMaxDiagramId", maxParams);
        int newDiagramId = toInt(maxRow != null ? maxRow.get("max_diagram_id") : null) + 1;

        // Node.js 동일 로직: "(n)" 접미사 제거해 baseName 추출 후 카운터 증가
        Pattern suffixPattern = Pattern.compile("^(.+?)(\\s*\\((\\d+)\\))?$");
        Matcher m = suffixPattern.matcher(diagramName);
        String baseName = (m.matches() && m.group(1) != null) ? m.group(1) : diagramName;

        // baseName으로 시작하는 기존 이름 조회
        Map<String, Object> nameCheckParams = new HashMap<>();
        nameCheckParams.put("pattern",     baseName + "%");
        nameCheckParams.put("company_code", companyCode);
        List<Map<String, Object>> existingNames = sqlSession.selectList("dataflow.getDiagramNameExists", nameCheckParams);

        // 가장 큰 (n) 번호 탐색
        Pattern numPattern = Pattern.compile("\\((\\d+)\\)$");
        int maxNumber = existingNames.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);

        String newDiagramName = baseName + " (" + (maxNumber + 1) + ")";

        for (Map<String, Object> rel : existing) {
            Map<String, Object> insertParams = new HashMap<>();
            insertParams.put("diagram_id",        newDiagramId);
            insertParams.put("relationship_name", newDiagramName);
            insertParams.put("from_table_name",    rel.get("from_table_name"));
            insertParams.put("from_column_name",   rel.get("from_column_name"));
            insertParams.put("to_table_name",      rel.get("to_table_name"));
            insertParams.put("to_column_name",     rel.get("to_column_name"));
            insertParams.put("relationship_type", rel.get("relationship_type"));
            insertParams.put("connection_type",   rel.get("connection_type"));
            insertParams.put("company_code",      companyCode);
            insertParams.put("settings",         toJsonString(rel.get("settings")));
            insertParams.put("created_by",        "system");
            sqlSession.insert("dataflow.insertTableRelationship", insertParams);
        }

        return newDiagramName;
    }

    @Transactional
    public int deleteDiagram(String companyCode, String diagramName) {
        Map<String, Object> queryParams = new HashMap<>();
        queryParams.put("company_code", companyCode);
        queryParams.put("diagram_name", diagramName);
        List<Map<String, Object>> existing = sqlSession.selectList("dataflow.getDiagramRelationshipListByName", queryParams);

        if (existing == null || existing.isEmpty()) return 0;

        int diagramId = toInt(existing.get(0).get("diagram_id"));

        Map<String, Object> deleteParams = new HashMap<>();
        deleteParams.put("diagram_id",   diagramId);
        deleteParams.put("company_code", companyCode);
        sqlSession.update("dataflow.softDeleteDiagramByDiagramId", deleteParams);

        return existing.size();
    }

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

    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;
        }
    }

    private void copyIfPresent(Map<String, Object> src, Map<String, Object> dest, String key) {
        if (src.containsKey(key)) dest.put(key, src.get(key));
    }

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

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