package com.erp.service;

import com.erp.common.BaseService;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.util.*;

@Service
@Slf4j
public class ExternalCallConfigService extends BaseService {

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

    // ── 목록 조회 (페이지네이션) ───────────────────────────────────────────

    public Map<String, Object> getExternalCallConfigList(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        commonService.applyPagination(params);
        Number cntNum = sqlSession.selectOne("externalCallConfig.getExternalCallConfigListCnt", params);
        int totalCount = cntNum != null ? cntNum.intValue() : 0;
        List<Map<String, Object>> list = sqlSession.selectList("externalCallConfig.getExternalCallConfigList", params);
        list.forEach(this::parseConfigData);
        return commonService.buildListResponse(list, totalCount, params);
    }

    // ── 버튼 제어용 간소화 목록 ──────────────────────────────────────────

    public List<Map<String, Object>> getExternalCallConfigForButtonControl(String companyCode) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("is_active", "Y");
        commonService.applyCompanyCodeFilter(params);
        List<Map<String, Object>> configs = sqlSession.selectList("externalCallConfig.getExternalCallConfigList", params);

        List<Map<String, Object>> result = new ArrayList<>();
        for (Map<String, Object> config : configs) {
            parseConfigData(config);
            Map<String, Object> simplified = new LinkedHashMap<>();
            simplified.put("id", config.get("id") != null ? config.get("id").toString() : null);
            simplified.put("name", config.get("config_name"));
            simplified.put("description", config.get("description"));
            Object configData = config.get("config_data");
            if (configData instanceof Map) {
                @SuppressWarnings("unchecked")
                Map<String, Object> cd = (Map<String, Object>) configData;
                @SuppressWarnings("unchecked")
                Map<String, Object> restApi = (Map<String, Object>) cd.getOrDefault("rest_api_settings", new HashMap<>());
                simplified.put("api_url", restApi.getOrDefault("api_url", ""));
                simplified.put("method", restApi.getOrDefault("http_method", "GET"));
                simplified.put("has_data_mapping", cd.containsKey("data_mapping_config"));
            } else {
                simplified.put("api_url", "");
                simplified.put("method", "GET");
                simplified.put("has_data_mapping", false);
            }
            result.add(simplified);
        }
        return result;
    }

    // ── 단건 조회 ────────────────────────────────────────────────────────

    public Map<String, Object> getExternalCallConfigInfo(Map<String, Object> params) {
        Map<String, Object> config = sqlSession.selectOne("externalCallConfig.getExternalCallConfigInfo", params);
        if (config != null) parseConfigData(config);
        return config;
    }

    // ── 생성 ─────────────────────────────────────────────────────────────

    @Transactional
    public Map<String, Object> insertExternalCallConfig(Map<String, Object> params) {
        String configName = str(params, "config_name");
        String callType = str(params, "call_type");
        Object configData = params.get("config_data");

        if (configName == null || configName.isBlank()) {
            throw new IllegalArgumentException("필수 필드가 누락되었습니다: config_name");
        }
        if (callType == null || callType.isBlank()) {
            throw new IllegalArgumentException("필수 필드가 누락되었습니다: call_type");
        }
        if (configData == null) {
            throw new IllegalArgumentException("필수 필드가 누락되었습니다: config_data");
        }

        // config_data Map → JSON string
        serializeConfigData(params);

        if (params.get("is_active") == null) params.put("is_active", "Y");

        sqlSession.insert("externalCallConfig.insertExternalCallConfig", params);

        Long id = params.get("id") != null ? Long.parseLong(params.get("id").toString()) : null;
        if (id != null) {
            Map<String, Object> infoParams = new HashMap<>();
            infoParams.put("id", id);
            return getExternalCallConfigInfo(infoParams);
        }
        parseConfigData(params);
        return params;
    }

    // ── 수정 ─────────────────────────────────────────────────────────────

    @Transactional
    public Map<String, Object> updateExternalCallConfig(Map<String, Object> params) {
        Long id = toLong(params, "id");
        if (id == null) throw new IllegalArgumentException("id가 필요합니다.");

        Map<String, Object> infoParams = new HashMap<>();
        infoParams.put("id", id);
        if (getExternalCallConfigInfo(infoParams) == null) {
            throw new NoSuchElementException("외부 호출 설정을 찾을 수 없습니다: ID " + id);
        }

        serializeConfigData(params);
        sqlSession.update("externalCallConfig.updateExternalCallConfig", params);

        return getExternalCallConfigInfo(infoParams);
    }

    // ── 삭제 (논리 삭제) ─────────────────────────────────────────────────

    @Transactional
    public void deleteExternalCallConfig(Map<String, Object> params) {
        int affected = sqlSession.delete("externalCallConfig.deleteExternalCallConfig", params);
        if (affected == 0) {
            throw new NoSuchElementException("외부 호출 설정을 찾을 수 없습니다.");
        }
    }

    // ── 설정 테스트 ───────────────────────────────────────────────────────

    public Map<String, Object> testExternalCallConfig(int id) {
        Map<String, Object> infoParams = new HashMap<>();
        infoParams.put("id", id);
        Map<String, Object> config = getExternalCallConfigInfo(infoParams);

        if (config == null) {
            return resultMap(false, "외부 호출 설정을 찾을 수 없습니다: ID " + id);
        }

        Object configDataRaw = config.get("config_data");
        if (!(configDataRaw instanceof Map)) {
            return resultMap(false, "config_data가 유효하지 않습니다.");
        }
        @SuppressWarnings("unchecked")
        Map<String, Object> configData = (Map<String, Object>) configDataRaw;

        String apiType = str(config, "api_type");
        String callType = str(config, "call_type");

        // Node.js와 동일한 검증 로직
        if ("discord".equals(apiType) || "slack".equals(apiType)) {
            if (!configData.containsKey("webhook_url")) {
                return resultMap(false, ("discord".equals(apiType) ? "Discord" : "Slack") + " 웹훅 URL이 필요합니다.");
            }
        } else if ("kakao-talk".equals(apiType)) {
            if (!configData.containsKey("access_token")) {
                return resultMap(false, "카카오톡 액세스 토큰이 필요합니다.");
            }
        } else if ("rest-api".equals(callType)) {
            @SuppressWarnings("unchecked")
            Map<String, Object> restApi = (Map<String, Object>) configData.getOrDefault("rest_api_settings", new HashMap<>());
            if (restApi.get("api_url") == null) {
                return resultMap(false, "API URL이 필요합니다.");
            }
        }

        return resultMap(true, "설정이 유효합니다.");
    }

    // ── 설정 실행 ────────────────────────────────────────────────────────

    public Map<String, Object> executeExternalCallConfig(int id, Map<String, Object> contextData) {
        long startTime = System.currentTimeMillis();
        Map<String, Object> infoParams = new HashMap<>();
        infoParams.put("id", id);
        Map<String, Object> config = getExternalCallConfigInfo(infoParams);

        if (config == null) {
            Map<String, Object> r = resultMap(false, "외부 호출 설정을 찾을 수 없습니다: ID " + id);
            r.put("execution_time", System.currentTimeMillis() - startTime);
            return r;
        }

        Object configDataRaw = config.get("config_data");
        if (!(configDataRaw instanceof Map)) {
            Map<String, Object> r = resultMap(false, "config_data가 유효하지 않습니다.");
            r.put("execution_time", System.currentTimeMillis() - startTime);
            return r;
        }

        @SuppressWarnings("unchecked")
        Map<String, Object> configData = (Map<String, Object>) configDataRaw;
        @SuppressWarnings("unchecked")
        Map<String, Object> restApi = (Map<String, Object>) configData.getOrDefault("rest_api_settings", new HashMap<>());

        String apiUrl = str(restApi, "api_url");
        if (apiUrl == null || apiUrl.isBlank()) {
            Map<String, Object> r = resultMap(false, "REST API URL이 설정되지 않았습니다.");
            r.put("execution_time", System.currentTimeMillis() - startTime);
            return r;
        }

        String httpMethod = str(restApi, "http_method");
        if (httpMethod == null) httpMethod = "GET";

        try {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.APPLICATION_JSON);
            @SuppressWarnings("unchecked")
            Map<String, Object> customHeaders = (Map<String, Object>) restApi.getOrDefault("headers", new HashMap<>());
            customHeaders.forEach((k, v) -> headers.set(k, v != null ? v.toString() : ""));

            String body = null;
            if (List.of("POST", "PUT", "PATCH").contains(httpMethod.toUpperCase())) {
                body = objectMapper.writeValueAsString(contextData);
            }

            RequestEntity<String> requestEntity = new RequestEntity<>(
                body, headers, HttpMethod.valueOf(httpMethod.toUpperCase()), URI.create(apiUrl));

            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<String> response = restTemplate.exchange(requestEntity, String.class);

            long executionTime = System.currentTimeMillis() - startTime;
            boolean success = response.getStatusCode().is2xxSuccessful();

            Object responseData = null;
            try {
                responseData = objectMapper.readValue(response.getBody(), Object.class);
            } catch (Exception ignored) {
                responseData = response.getBody();
            }

            Map<String, Object> result = new LinkedHashMap<>();
            result.put("success", success);
            result.put("message", success ? "외부호출 '" + config.get("config_name") + "' 실행 완료" : "외부호출 실행 실패");
            result.put("data", responseData);
            result.put("execution_time", executionTime);
            return result;

        } catch (Exception e) {
            log.error("외부호출 실행 오류 (id={}): {}", id, e.getMessage());
            Map<String, Object> result = resultMap(false, "외부호출 실행 실패: " + e.getMessage());
            result.put("execution_time", System.currentTimeMillis() - startTime);
            result.put("error", e.getMessage());
            return result;
        }
    }

    // ── 내부 유틸 ─────────────────────────────────────────────────────────

    @SuppressWarnings("unchecked")
    private void parseConfigData(Map<String, Object> row) {
        String key = "config_data";
        Object raw = row.get(key);
        if (raw instanceof String s && !s.isBlank()) {
            try {
                row.put(key, objectMapper.readValue(s, new TypeReference<Map<String, Object>>() {}));
            } catch (Exception e) {
                // 파싱 실패 시 원본 유지
            }
        }
    }

    private void serializeConfigData(Map<String, Object> params) {
        Object raw = params.get("config_data");
        if (raw instanceof Map || raw instanceof List) {
            try {
                params.put("config_data", objectMapper.writeValueAsString(raw));
            } catch (Exception e) {
                params.put("config_data", "{}");
            }
        } else if (raw == null) {
            params.put("config_data", "{}");
        }
    }

    private Map<String, Object> resultMap(boolean success, String message) {
        Map<String, Object> r = new LinkedHashMap<>();
        r.put("success", success);
        r.put("message", message);
        return r;
    }

    private String str(Map<String, Object> m, String key) {
        Object v = m.get(key);
        return v != null ? v.toString() : null;
    }

    private Long toLong(Map<String, Object> m, String key) {
        Object v = m.get(key);
        if (v == null) return null;
        try { return Long.parseLong(v.toString()); } catch (Exception e) { return null; }
    }
}
