package com.erp.controller;

import com.erp.dto.ApiResponse;
import com.erp.service.FlowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

/**
 * Flow API Controller
 * Base path: /api/flows
 *
 * Definitions : POST/GET/GET-detail/PUT/DELETE  /definitions[/:id]
 * Steps       : GET/POST  /definitions/:flowId/steps
 *               PUT/DELETE /steps/:stepId
 * Connections : GET /connections/:flowId
 *               POST /connections
 *               DELETE /connections/:connectionId
 * Execution   : GET /:flowId/step/:stepId/count|list|column-labels
 *               GET /:flowId/steps/counts
 *               PUT /:flowId/step/:stepId/data/:recordId
 * Data move   : POST /move, /move-batch
 * Audit       : GET /audit/:flowId, /audit/:flowId/:recordId
 * Procedures  : GET /procedures, /procedures/:name/parameters
 */
@RestController
@RequestMapping("/api/flows")
@RequiredArgsConstructor
@Slf4j
public class FlowController {

    private final FlowService service;

    // ══════════════════════════════════════════════════════════
    // Definitions
    // ══════════════════════════════════════════════════════════

    @GetMapping("/definitions")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowDefinitionList(
            @RequestAttribute("company_code") String companyCode,
            @RequestParam Map<String, Object> params) {
        params.put("company_code", companyCode);
        return ResponseEntity.ok(ApiResponse.success(service.getFlowDefinitionList(params)));
    }

    @GetMapping("/definitions/{id}")
    public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowDefinitionInfo(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("id") int id) {
        Map<String, Object> detail = service.getFlowDefinitionInfo(id, companyCode);
        if (detail == null) {
            return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
        }
        return ResponseEntity.ok(ApiResponse.success(detail));
    }

    @PostMapping("/definitions")
    public ResponseEntity<ApiResponse<Map<String, Object>>> insertFlowDefinition(
            @RequestAttribute("company_code") String companyCode,
            @RequestAttribute("user_id") String userId,
            @RequestBody Map<String, Object> body) {

        if (body.get("name") == null) {
            return ResponseEntity.status(400).body(ApiResponse.error("Name is required"));
        }

        // 테이블 존재 확인 (REST API / multi 제외)
        String tableName  = (String) body.get("table_name");
        String dbSource   = (String) body.getOrDefault("db_source_type", "internal");
        boolean skipCheck = "restapi".equals(dbSource)
                || "multi_restapi".equals(dbSource)
                || "multi_external_db".equals(dbSource)
                || (tableName != null && (tableName.startsWith("_restapi_")
                        || tableName.startsWith("_multi_")));

        if (tableName != null && !skipCheck && !service.checkTableExists(tableName)) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("Table '" + tableName + "' does not exist"));
        }

        try {
            Map<String, Object> created = service.insertFlowDefinition(body, userId, companyCode);
            return ResponseEntity.ok(ApiResponse.success(created));
        } catch (Exception e) {
            log.error("플로우 정의 생성 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to create flow definition"));
        }
    }

    @PutMapping("/definitions/{id}")
    public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlowDefinition(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("id") int id,
            @RequestBody Map<String, Object> body) {
        if (service.getFlowDefinitionById(id, companyCode) == null) {
            return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
        }
        try {
            Map<String, Object> updated = service.updateFlowDefinition(id, body, companyCode);
            return ResponseEntity.ok(ApiResponse.success(updated));
        } catch (Exception e) {
            log.error("플로우 정의 수정 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to update flow definition"));
        }
    }

    @DeleteMapping("/definitions/{id}")
    public ResponseEntity<ApiResponse<Void>> deleteFlowDefinition(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("id") int id) {
        boolean deleted = service.deleteFlowDefinition(id, companyCode);
        if (!deleted) {
            return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
        }
        return ResponseEntity.ok(ApiResponse.success(null, "Flow definition deleted successfully"));
    }

    // ══════════════════════════════════════════════════════════
    // Steps  (under /definitions/:flowId/steps  or  /steps/:stepId)
    // ══════════════════════════════════════════════════════════

    @GetMapping("/definitions/{flowId}/steps")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowStepList(
            @PathVariable("flow_id") int flowId) {
        return ResponseEntity.ok(ApiResponse.success(service.getFlowStepList(flowId)));
    }

    @PostMapping("/definitions/{flowId}/steps")
    public ResponseEntity<ApiResponse<Map<String, Object>>> insertFlowStep(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("flow_id") int flowId,
            @RequestBody Map<String, Object> body) {

        if (body.get("step_name") == null || body.get("step_order") == null) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("stepName and stepOrder are required"));
        }
        // 플로우 소유권 확인
        if (service.getFlowDefinitionById(flowId, companyCode) == null) {
            return ResponseEntity.status(404)
                    .body(ApiResponse.error("Flow definition not found or access denied"));
        }
        try {
            Map<String, Object> step = service.insertFlowStep(flowId, body);
            return ResponseEntity.ok(ApiResponse.success(step));
        } catch (Exception e) {
            log.error("플로우 스텝 생성 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to create flow step"));
        }
    }

    @PutMapping("/steps/{stepId}")
    public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlowStep(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("step_id") int stepId,
            @RequestBody Map<String, Object> body) {

        // 스텝 소유권 검증: 스텝이 속한 플로우가 요청자 회사 소유인지 확인
        Map<String, Object> existingStep = service.getFlowStepById(stepId);
        if (existingStep != null) {
            int flowDefId = toInt(existingStep.get("flow_definition_id"), 0);
            if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
                return ResponseEntity.status(403)
                        .body(ApiResponse.error("Access denied: flow does not belong to your company"));
            }
        }

        try {
            Map<String, Object> step = service.updateFlowStep(stepId, body);
            if (step == null) {
                return ResponseEntity.status(404).body(ApiResponse.error("Flow step not found"));
            }
            return ResponseEntity.ok(ApiResponse.success(step));
        } catch (Exception e) {
            log.error("플로우 스텝 수정 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to update flow step"));
        }
    }

    @DeleteMapping("/steps/{stepId}")
    public ResponseEntity<ApiResponse<Void>> deleteFlowStep(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("step_id") int stepId) {

        Map<String, Object> existingStep = service.getFlowStepById(stepId);
        if (existingStep != null) {
            int flowDefId = toInt(existingStep.get("flow_definition_id"), 0);
            if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
                return ResponseEntity.status(403)
                        .body(ApiResponse.error("Access denied: flow does not belong to your company"));
            }
        }

        boolean deleted = service.deleteFlowStep(stepId);
        if (!deleted) {
            return ResponseEntity.status(404).body(ApiResponse.error("Flow step not found"));
        }
        return ResponseEntity.ok(ApiResponse.success(null, "Flow step deleted successfully"));
    }

    // ══════════════════════════════════════════════════════════
    // Connections
    // ══════════════════════════════════════════════════════════

    @GetMapping("/connections/{flowId}")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowConnectionList(
            @PathVariable("flow_id") int flowId) {
        return ResponseEntity.ok(ApiResponse.success(service.getFlowConnectionList(flowId)));
    }

    @PostMapping("/connections")
    public ResponseEntity<ApiResponse<Map<String, Object>>> insertFlowConnection(
            @RequestAttribute("company_code") String companyCode,
            @RequestBody Map<String, Object> body) {

        Object flowDefIdRaw = body.get("flow_definition_id");
        Object fromRaw      = body.get("from_step_id");
        Object toRaw        = body.get("to_step_id");

        if (flowDefIdRaw == null || fromRaw == null || toRaw == null) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("flowDefinitionId, fromStepId, and toStepId are required"));
        }

        int flowDefId = toInt(flowDefIdRaw, 0);
        int fromStepId = toInt(fromRaw, 0);
        int toStepId   = toInt(toRaw, 0);

        // 플로우 소유권 확인
        if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
            return ResponseEntity.status(404)
                    .body(ApiResponse.error("Flow definition not found or access denied"));
        }
        // 스텝 소속 검증
        Map<String, Object> fromStep = service.getFlowStepById(fromStepId);
        Map<String, Object> toStep   = service.getFlowStepById(toStepId);
        if (fromStep == null || !String.valueOf(fromStep.get("flow_definition_id")).equals(String.valueOf(flowDefId))
         || toStep == null   || !String.valueOf(toStep.get("flow_definition_id")).equals(String.valueOf(flowDefId))) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("fromStepId and toStepId must belong to the specified flow"));
        }

        try {
            return ResponseEntity.ok(ApiResponse.success(service.insertFlowConnection(body)));
        } catch (Exception e) {
            log.error("플로우 연결 생성 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to create connection"));
        }
    }

    @DeleteMapping("/connections/{connectionId}")
    public ResponseEntity<ApiResponse<Void>> deleteFlowConnection(
            @RequestAttribute("company_code") String companyCode,
            @PathVariable("connection_id") int connectionId) {

        Map<String, Object> existingConn = service.getFlowConnectionById(connectionId);
        if (existingConn != null) {
            int flowDefId = toInt(existingConn.get("flow_definition_id"), 0);
            if (service.getFlowDefinitionById(flowDefId, companyCode) == null) {
                return ResponseEntity.status(403)
                        .body(ApiResponse.error("Access denied: flow does not belong to your company"));
            }
        }
        boolean deleted = service.deleteFlowConnection(connectionId);
        if (!deleted) {
            return ResponseEntity.status(404).body(ApiResponse.error("Connection not found"));
        }
        return ResponseEntity.ok(ApiResponse.success(null, "Connection deleted successfully"));
    }

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

    @GetMapping("/{flowId}/step/{stepId}/count")
    public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowStepDataCount(
            @PathVariable("flow_id") int flowId,
            @PathVariable("step_id") int stepId) {
        try {
            return ResponseEntity.ok(ApiResponse.success(service.getFlowStepDataCount(flowId, stepId)));
        } catch (Exception e) {
            log.error("스텝 카운트 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to get step data count"));
        }
    }

    @GetMapping("/{flowId}/step/{stepId}/list")
    public ResponseEntity<ApiResponse<Object>> getFlowStepDataList(
            @PathVariable("flow_id")  int flowId,
            @PathVariable("step_id")  int stepId,
            @RequestParam(defaultValue = "1")  int page,
            @RequestParam(defaultValue = "20") int pageSize) {
        try {
            return ResponseEntity.ok(ApiResponse.success(
                    service.getFlowStepDataList(flowId, stepId, page, pageSize)));
        } catch (Exception e) {
            log.error("스텝 데이터 목록 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to get step data list"));
        }
    }

    @GetMapping("/{flowId}/step/{stepId}/column-labels")
    public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowColumnLabelList(
            @PathVariable("flow_id") int flowId,
            @PathVariable("step_id") int stepId) {
        try {
            Map<String, Object> step = service.getFlowStepById(stepId);
            if (step == null) {
                return ResponseEntity.status(404).body(ApiResponse.error("Step not found"));
            }
            Map<String, Object> flow = service.getFlowDefinitionById(flowId, "*");
            if (flow == null) {
                return ResponseEntity.status(404).body(ApiResponse.error("Flow definition not found"));
            }
            Map<String, Object> labels = service.getFlowColumnLabelList(flowId, stepId);
            return ResponseEntity.ok(ApiResponse.success(labels != null ? labels : Map.of()));
        } catch (Exception e) {
            log.error("컬럼 라벨 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to get step column labels"));
        }
    }

    @GetMapping("/{flowId}/steps/counts")
    public ResponseEntity<ApiResponse<Map<String, Object>>> getFlowStepCountList(
            @PathVariable("flow_id") int flowId) {
        try {
            return ResponseEntity.ok(ApiResponse.success(service.getFlowStepCountList(flowId)));
        } catch (Exception e) {
            log.error("전체 스텝 카운트 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to get all step counts"));
        }
    }

    @PutMapping("/{flowId}/step/{stepId}/data/{recordId}")
    public ResponseEntity<ApiResponse<Map<String, Object>>> updateFlowStepData(
            @RequestAttribute("company_code") String companyCode,
            @RequestAttribute("user_id") String userId,
            @PathVariable("flow_id")   int flowId,
            @PathVariable("step_id")   int stepId,
            @PathVariable("record_id") String recordId,
            @RequestBody Map<String, Object> updateData) {

        if (updateData == null || updateData.isEmpty()) {
            return ResponseEntity.status(400).body(ApiResponse.error("Update data is required"));
        }
        try {
            Map<String, Object> result =
                    service.updateFlowStepData(flowId, stepId, recordId, updateData, userId, companyCode);
            return ResponseEntity.ok(ApiResponse.success(result, "Data updated successfully"));
        } catch (IllegalArgumentException e) {
            return ResponseEntity.status(404).body(ApiResponse.error(e.getMessage()));
        } catch (Exception e) {
            log.error("스텝 데이터 수정 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to update step data"));
        }
    }

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

    @PostMapping("/move")
    public ResponseEntity<ApiResponse<Void>> moveData(
            @RequestAttribute("user_id") String userId,
            @RequestBody Map<String, Object> body) {

        Object flowId    = body.get("flow_id");
        Object fromStepId = body.get("from_step_id");
        Object recordId  = body.get("record_id");
        Object toStepId  = body.get("to_step_id");

        if (flowId == null || fromStepId == null || recordId == null || toStepId == null) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("flowId, fromStepId, recordId, and toStepId are required"));
        }
        try {
            service.moveDataToStep(
                toInt(flowId, 0), toInt(fromStepId, 0), toInt(toStepId, 0),
                recordId.toString(), userId, (String) body.get("note"));
            return ResponseEntity.ok(ApiResponse.success(null, "Data moved successfully"));
        } catch (Exception e) {
            log.error("데이터 이동 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to move data"));
        }
    }

    @PostMapping("/move-batch")
    public ResponseEntity<ApiResponse<Map<String, Object>>> moveBatchData(
            @RequestAttribute("user_id") String userId,
            @RequestBody Map<String, Object> body) {

        Object flowId    = body.get("flow_id");
        Object fromStepId = body.get("from_step_id");
        Object toStepId  = body.get("to_step_id");
        Object dataIds   = body.get("data_ids");

        if (flowId == null || fromStepId == null || toStepId == null
                || !(dataIds instanceof List)) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error(
                            "flowId, fromStepId, toStepId, and dataIds (array) are required"));
        }

        @SuppressWarnings("unchecked")
        List<Object> ids = (List<Object>) dataIds;

        try {
            Map<String, Object> result = service.moveBatchData(
                toInt(flowId, 0), toInt(fromStepId, 0), toInt(toStepId, 0), ids, userId);

            long successCount = ids.stream()
                    .filter(id -> result.get("results") instanceof List<?> list
                            && list.stream().anyMatch(r ->
                                r instanceof Map<?, ?> m
                                && Boolean.TRUE.equals(m.get("success"))
                                && id.toString().equals(String.valueOf(m.get("id")))))
                    .count();
            long failureCount = ids.size() - successCount;

            boolean overallSuccess = (boolean) result.get("success");
            String message = overallSuccess
                    ? successCount + "건의 데이터를 성공적으로 이동했습니다"
                    : successCount + "건 성공, " + failureCount + "건 실패";

            Map<String, Object> data = new java.util.LinkedHashMap<>();
            data.put("success_count", successCount);
            data.put("failure_count", failureCount);
            data.put("total", ids.size());
            result.put("message", message);
            result.put("data", data);

            return ResponseEntity.ok(ApiResponse.success(result));
        } catch (Exception e) {
            log.error("배치 데이터 이동 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to move batch data"));
        }
    }

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

    /** GET /audit/:flowId — 플로우 전체 이력 */
    @GetMapping("/audit/{flowId}")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowAuditLogList(
            @PathVariable("flow_id") int flowId,
            @RequestParam(defaultValue = "100") int limit) {
        try {
            return ResponseEntity.ok(ApiResponse.success(service.getFlowAuditLogList(flowId, limit)));
        } catch (Exception e) {
            log.error("플로우 감사 로그 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to get flow audit logs"));
        }
    }

    /** GET /audit/:flowId/:recordId — 특정 레코드 이력 */
    @GetMapping("/audit/{flowId}/{recordId}")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowAuditLogListByRecord(
            @PathVariable("flow_id")   int flowId,
            @PathVariable("record_id") String recordId) {
        try {
            return ResponseEntity.ok(ApiResponse.success(service.getFlowAuditLogListByRecord(flowId, recordId)));
        } catch (Exception e) {
            log.error("감사 로그 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "Failed to get audit logs"));
        }
    }

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

    @GetMapping("/procedures")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowProcedureList(
            @RequestParam(required = false) String dbSource,
            @RequestParam(required = false) Integer connectionId,
            @RequestParam(required = false) String schema) {

        if (dbSource != null && !"internal".equals(dbSource) && !"external".equals(dbSource)) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("dbSource는 internal 또는 external이어야 합니다"));
        }
        if ("external".equals(dbSource) && connectionId == null) {
            return ResponseEntity.status(400)
                    .body(ApiResponse.error("외부 DB 조회 시 connectionId가 필요합니다"));
        }

        try {
            // 현재 구현은 internal DB만 지원 (external은 별도 연결 관리가 필요)
            return ResponseEntity.ok(ApiResponse.success(service.getFlowProcedureList(schema)));
        } catch (Exception e) {
            log.error("프로시저 목록 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "프로시저 목록 조회에 실패했습니다"));
        }
    }

    @GetMapping("/procedures/{name}/parameters")
    public ResponseEntity<ApiResponse<List<Map<String, Object>>>> getFlowProcedureParameterList(
            @PathVariable("name") String name,
            @RequestParam(required = false) String dbSource,
            @RequestParam(required = false) Integer connectionId,
            @RequestParam(required = false) String schema) {

        if (name == null || name.isBlank()) {
            return ResponseEntity.status(400).body(ApiResponse.error("프로시저 이름이 필요합니다"));
        }
        try {
            return ResponseEntity.ok(ApiResponse.success(
                    service.getFlowProcedureParameterList(name, schema)));
        } catch (Exception e) {
            log.error("프로시저 파라미터 조회 실패", e);
            return ResponseEntity.status(500)
                    .body(ApiResponse.error(e.getMessage() != null ? e.getMessage()
                            : "프로시저 파라미터 조회에 실패했습니다"));
        }
    }

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