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.*;

@Service
@Slf4j
public class FlowService extends BaseService {

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

    // ══════════════════════════════════════════════════════════
    // Flow Definitions
    // ══════════════════════════════════════════════════════════

    public List<Map<String, Object>> getFlowDefinitionList(Map<String, Object> params) {
        return sqlSession.selectList("flow.getFlowDefinitionList", params);
    }

    public Map<String, Object> getFlowDefinitionInfo(int id, String companyCode) {
        Map<String, Object> params = Map.of("id", id, "company_code", companyCode);
        Map<String, Object> definition = sqlSession.selectOne("flow.getFlowDefinitionInfo", params);
        if (definition == null) return null;

        Map<String, Object> stepParams = Map.of("flow_id", id);
        List<Map<String, Object>> steps = sqlSession.selectList("flow.getFlowStepList", stepParams);
        List<Map<String, Object>> connections = sqlSession.selectList("flow.getFlowConnectionList", stepParams);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("definition", definition);
        result.put("steps", steps);
        result.put("connections", connections);
        return result;
    }

    /** company_code 에 상관없이 단순 존재 확인 (소유권 검증용) */
    public Map<String, Object> getFlowDefinitionById(int id, String companyCode) {
        return sqlSession.selectOne("flow.getFlowDefinitionInfo", Map.of("id", id, "company_code", companyCode));
    }

    public boolean checkTableExists(String tableName) {
        Number n = sqlSession.selectOne("flow.checkTableExists", Map.of("table_name", tableName));
        return (n != null ? n.intValue() : 0) > 0;
    }

    @Transactional
    public Map<String, Object> insertFlowDefinition(Map<String, Object> body,
                                                     String userId,
                                                     String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("created_by", userId);
        params.put("name",                  body.get("name"));
        params.put("description",           body.get("description"));
        params.put("table_name",             body.get("table_name"));
        params.put("db_source_type",          body.getOrDefault("db_source_type", "internal"));
        params.put("db_connection_id",        body.get("db_connection_id"));
        params.put("rest_api_connection_id",   body.get("rest_api_connection_id"));
        params.put("rest_api_endpoint",       body.get("rest_api_endpoint"));
        params.put("rest_api_json_path",       body.get("rest_api_json_path"));
        params.put("rest_api_connections",    toJsonString(body.get("rest_api_connections")));
        params.put("is_active",              body.getOrDefault("is_active", true));

        sqlSession.insert("flow.insertFlowDefinition", params);
        return getFlowDefinitionById(toInt(params.get("id"), 0), companyCode);
    }

    @Transactional
    public Map<String, Object> updateFlowDefinition(int id,
                                                     Map<String, Object> body,
                                                     String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("id", id);
        params.put("company_code", companyCode);
        if (body.containsKey("name"))        params.put("name",        body.get("name"));
        if (body.containsKey("description")) params.put("description", body.get("description"));
        if (body.containsKey("is_active"))    params.put("is_active",    body.get("is_active"));

        sqlSession.update("flow.updateFlowDefinition", params);
        return getFlowDefinitionById(id, companyCode);
    }

    @Transactional
    public boolean deleteFlowDefinition(int id, String companyCode) {
        if (getFlowDefinitionById(id, companyCode) == null) return false;
        sqlSession.delete("flow.deleteFlowDefinition", Map.of("id", id, "company_code", companyCode));
        return true;
    }

    // ══════════════════════════════════════════════════════════
    // Flow Steps
    // ══════════════════════════════════════════════════════════

    public List<Map<String, Object>> getFlowStepList(int flowId) {
        return sqlSession.selectList("flow.getFlowStepList", Map.of("flow_id", flowId));
    }

    public Map<String, Object> getFlowStepById(int id) {
        return sqlSession.selectOne("flow.getFlowStepInfo", Map.of("id", id));
    }

    @Transactional
    public Map<String, Object> insertFlowStep(int flowDefinitionId,
                                               Map<String, Object> body) {
        Map<String, Object> params = new HashMap<>();
        params.put("flow_definition_id", flowDefinitionId);
        params.put("step_name",         body.get("step_name"));
        params.put("step_order",        body.get("step_order"));
        params.put("table_name",        body.get("table_name"));
        params.put("condition_json",    toJsonString(body.get("condition_json")));
        params.put("color",            body.get("color"));
        params.put("position_x",        body.get("position_x"));
        params.put("position_y",        body.get("position_y"));
        params.put("move_type",         body.get("move_type"));
        params.put("status_column",     body.get("status_column"));
        params.put("status_value",      body.get("status_value"));
        params.put("target_table",      body.get("target_table"));
        params.put("field_mappings",    toJsonString(body.get("field_mappings")));
        params.put("integration_type",  body.get("integration_type"));
        params.put("integration_config",toJsonString(body.get("integration_config")));
        params.put("display_config",    toJsonString(body.get("display_config")));

        sqlSession.insert("flow.insertFlowStep", params);
        return getFlowStepById(toInt(params.get("id"), 0));
    }

    @Transactional
    public Map<String, Object> updateFlowStep(int stepId, Map<String, Object> body) {
        Map<String, Object> params = new HashMap<>();
        params.put("id", stepId);
        copyIfPresent(body, params, "step_name");
        copyIfPresent(body, params, "step_order");
        copyIfPresent(body, params, "table_name");
        copyIfPresent(body, params, "color");
        copyIfPresent(body, params, "position_x");
        copyIfPresent(body, params, "position_y");
        copyIfPresent(body, params, "move_type");
        copyIfPresent(body, params, "status_column");
        copyIfPresent(body, params, "status_value");
        copyIfPresent(body, params, "target_table");
        copyIfPresent(body, params, "integration_type");
        // JSON 필드
        if (body.containsKey("condition_json"))   params.put("condition_json",   toJsonString(body.get("condition_json")));
        if (body.containsKey("field_mappings"))   params.put("field_mappings",   toJsonString(body.get("field_mappings")));
        if (body.containsKey("integration_config")) params.put("integration_config", toJsonString(body.get("integration_config")));
        if (body.containsKey("display_config"))   params.put("display_config",   toJsonString(body.get("display_config")));

        int affected = sqlSession.update("flow.updateFlowStep", params);
        if (affected == 0) return null;
        return getFlowStepById(stepId);
    }

    @Transactional
    public boolean deleteFlowStep(int stepId) {
        if (getFlowStepById(stepId) == null) return false;
        sqlSession.delete("flow.deleteFlowStep", Map.of("id", stepId));
        return true;
    }

    // ══════════════════════════════════════════════════════════
    // Flow Connections
    // ══════════════════════════════════════════════════════════

    public List<Map<String, Object>> getFlowConnectionList(int flowId) {
        return sqlSession.selectList("flow.getFlowConnectionList", Map.of("flow_id", flowId));
    }

    public Map<String, Object> getFlowConnectionById(int id) {
        return sqlSession.selectOne("flow.getFlowConnectionInfo", Map.of("id", id));
    }

    @Transactional
    public Map<String, Object> insertFlowConnection(Map<String, Object> body) {
        Map<String, Object> params = new HashMap<>();
        params.put("flow_definition_id", toInt(body.get("flow_definition_id"), 0));
        params.put("from_step_id",       toInt(body.get("from_step_id"), 0));
        params.put("to_step_id",         toInt(body.get("to_step_id"), 0));
        params.put("label",            body.get("label"));
        sqlSession.insert("flow.insertFlowConnection", params);
        return getFlowConnectionById(toInt(params.get("id"), 0));
    }

    @Transactional
    public boolean deleteFlowConnection(int id) {
        if (getFlowConnectionById(id) == null) return false;
        sqlSession.delete("flow.deleteFlowConnection", Map.of("id", id));
        return true;
    }

    // ══════════════════════════════════════════════════════════
    // Execution
    // ══════════════════════════════════════════════════════════

    public Map<String, Object> getFlowStepDataCount(int flowId, int stepId) {
        StepTableInfo info = resolveStepTableInfo(flowId, stepId, "*");
        if (info == null) return Map.of("count", 0);
        Number countNum = sqlSession.selectOne("flow.getFlowStepDataListCnt", info.toParams());
        int count = countNum != null ? countNum.intValue() : 0;
        return Map.of("count", count);
    }

    public Object getFlowStepDataList(int flowId, int stepId, int page, int pageSize) {
        StepTableInfo info = resolveStepTableInfo(flowId, stepId, "*");
        if (info == null) return List.of();

        Map<String, Object> params = info.toParams();
        params.put("limit", pageSize);
        params.put("offset", (page - 1) * pageSize);
        return sqlSession.selectList("flow.getFlowStepDataList", params);
    }

    public Map<String, Object> getFlowColumnLabelList(int flowId, int stepId) {
        Map<String, Object> step = getFlowStepById(stepId);
        if (step == null) return null;

        Map<String, Object> flow = getFlowDefinitionById(flowId, "*");
        if (flow == null) return null;

        String tableName = step.get("table_name") != null
                ? (String) step.get("table_name")
                : (String) flow.get("table_name");

        if (tableName == null) return Map.of();

        List<Map<String, Object>> rows = sqlSession.selectList("flow.getFlowColumnLabelList", Map.of("table_name", tableName));
        Map<String, Object> labels = new LinkedHashMap<>();
        for (Map<String, Object> row : rows) {
            String colName = (String) row.get("column_name");
            String colLabel = (String) row.get("column_label");
            if (colName != null && colLabel != null) labels.put(colName, colLabel);
        }
        return labels;
    }

    public Map<String, Object> getFlowStepCountList(int flowId) {
        List<Map<String, Object>> steps = getFlowStepList(flowId);
        Map<String, Object> flow = getFlowDefinitionById(flowId, "*");
        String flowTableName = flow != null ? (String) flow.get("table_name") : null;

        Map<String, Object> counts = new LinkedHashMap<>();
        for (Map<String, Object> step : steps) {
            int sId = toInt(step.get("id"), 0);
            String tableName = step.get("table_name") != null
                    ? (String) step.get("table_name") : flowTableName;

            if (tableName == null) {
                counts.put(String.valueOf(sId), 0);
                continue;
            }
            Map<String, Object> params = new HashMap<>();
            params.put("table_name",    tableName);
            params.put("status_column", step.get("status_column"));
            params.put("status_value",  step.get("status_value"));
            try {
                Number cntNum = sqlSession.selectOne("flow.getFlowStepDataListCnt", params);
                int cntVal = cntNum != null ? cntNum.intValue() : 0;
                counts.put(String.valueOf(sId), cntVal);
            } catch (Exception e) {
                log.warn("스텝 {} 카운트 조회 실패: {}", sId, e.getMessage());
                counts.put(String.valueOf(sId), 0);
            }
        }
        return counts;
    }

    @Transactional
    public Map<String, Object> updateFlowStepData(int flowId, int stepId,
                                                   String recordId,
                                                   Map<String, Object> updateData,
                                                   String userId,
                                                   String companyCode) {
        StepTableInfo info = resolveStepTableInfo(flowId, stepId, companyCode);
        if (info == null) {
            throw new IllegalArgumentException("스텝 또는 플로우를 찾을 수 없습니다.");
        }

        // updateData 의 각 필드를 status update 로 처리
        for (Map.Entry<String, Object> entry : updateData.entrySet()) {
            String col = entry.getKey();
            Object val = entry.getValue();
            Map<String, Object> params = new HashMap<>();
            params.put("table_name",     info.tableName());
            params.put("status_column",  col);
            params.put("to_status_value", val != null ? val.toString() : null);
            params.put("record_id",      recordId);
            sqlSession.update("flow.updateFlowRecordStatus", params);
        }
        return Map.of("updated", true, "record_id", recordId);
    }

    // ══════════════════════════════════════════════════════════
    // Data Move
    // ══════════════════════════════════════════════════════════

    @Transactional
    public void moveDataToStep(int flowId, int fromStepId, int toStepId,
                                String recordId, String userId, String note) {
        Map<String, Object> fromStep = getFlowStepById(fromStepId);
        Map<String, Object> toStep   = getFlowStepById(toStepId);
        if (fromStep == null || toStep == null) {
            throw new IllegalArgumentException("소스 또는 대상 스텝을 찾을 수 없습니다.");
        }

        Map<String, Object> flow = getFlowDefinitionById(flowId, "*");
        String tableName = fromStep.get("table_name") != null
                ? (String) fromStep.get("table_name")
                : (flow != null ? (String) flow.get("table_name") : null);

        if (tableName == null) {
            throw new IllegalArgumentException("스텝 테이블을 찾을 수 없습니다.");
        }

        String toStatusValue = (String) toStep.get("status_value");
        String statusColumn  = (String) fromStep.get("status_column");

        if (statusColumn != null && toStatusValue != null) {
            Map<String, Object> updateParams = new HashMap<>();
            updateParams.put("table_name",     tableName);
            updateParams.put("status_column",  statusColumn);
            updateParams.put("to_status_value", toStatusValue);
            updateParams.put("record_id",      recordId);
            sqlSession.update("flow.updateFlowRecordStatus", updateParams);
        }

        // 감사 로그
        Map<String, Object> logParams = new HashMap<>();
        logParams.put("flow_definition_id", flowId);
        logParams.put("record_id",    recordId);
        logParams.put("from_step_id",  fromStepId);
        logParams.put("to_step_id",    toStepId);
        logParams.put("from_step_name", fromStep.get("step_name"));
        logParams.put("to_step_name",  toStep.get("step_name"));
        logParams.put("moved_by",     userId);
        logParams.put("note",        note);
        logParams.put("metadata",    null);
        sqlSession.insert("flow.insertFlowAuditLog", logParams);
    }

    @Transactional
    public Map<String, Object> moveBatchData(int flowId, int fromStepId, int toStepId,
                                              List<Object> dataIds, String userId) {
        List<Map<String, Object>> results = new ArrayList<>();
        boolean overallSuccess = true;

        for (Object id : dataIds) {
            try {
                moveDataToStep(flowId, fromStepId, toStepId, id.toString(), userId, null);
                results.add(Map.of("id", id, "success", true));
            } catch (Exception e) {
                log.warn("배치 이동 실패 recordId={}: {}", id, e.getMessage());
                results.add(Map.of("id", id, "success", false, "message", e.getMessage()));
                overallSuccess = false;
            }
        }

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", overallSuccess);
        result.put("results", results);
        return result;
    }

    // ══════════════════════════════════════════════════════════
    // Audit Logs
    // ══════════════════════════════════════════════════════════

    public List<Map<String, Object>> getFlowAuditLogListByRecord(int flowId, String recordId) {
        return sqlSession.selectList("flow.getFlowAuditLogListByRecord", Map.of("flow_id", flowId, "record_id", recordId));
    }

    public List<Map<String, Object>> getFlowAuditLogList(int flowId, int limit) {
        Map<String, Object> params = new HashMap<>();
        params.put("flow_id", flowId);
        params.put("limit",  limit);
        return sqlSession.selectList("flow.getFlowAuditLogList", params);
    }

    // ══════════════════════════════════════════════════════════
    // Procedures
    // ══════════════════════════════════════════════════════════

    public List<Map<String, Object>> getFlowProcedureList(String schema) {
        Map<String, Object> params = new HashMap<>();
        params.put("schema", schema);
        return sqlSession.selectList("flow.getFlowProcedureList", params);
    }

    public List<Map<String, Object>> getFlowProcedureParameterList(String name, String schema) {
        return sqlSession.selectList("flow.getFlowProcedureParameterList",
                Map.of("name", name, "schema", schema != null ? schema : ""));
    }

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

    /** 스텝의 테이블/조건 정보를 집약 */
    private StepTableInfo resolveStepTableInfo(int flowId, int stepId, String companyCode) {
        Map<String, Object> step = getFlowStepById(stepId);
        if (step == null) return null;

        Map<String, Object> flow = getFlowDefinitionById(flowId, companyCode);
        String tableName = step.get("table_name") != null
                ? (String) step.get("table_name")
                : (flow != null ? (String) flow.get("table_name") : null);
        if (tableName == null) return null;

        return new StepTableInfo(
            tableName,
            (String) step.get("status_column"),
            (String) step.get("status_value")
        );
    }

    private record StepTableInfo(String tableName, String statusColumn, String statusValue) {
        Map<String, Object> toParams() {
            Map<String, Object> p = new HashMap<>();
            p.put("table_name",    tableName);
            p.put("status_column", statusColumn);
            p.put("status_value",  statusValue);
            return p;
        }
    }

    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) && src.get(key) != null) dest.put(key, src.get(key));
    }

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