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.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.*;

@Service
@Slf4j
public class ExternalRestApiConnectionService extends BaseService {

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

    @Value("${erp.encryption.secret:default-secret-key-change-in-production}")
    private String encryptionKey;

    private static final String ALGORITHM = "AES/GCM/NoPadding";
    private static final int GCM_TAG_LENGTH = 128;
    private static final int IV_LENGTH = 16;

    // ── 목록 조회 ──────────────────────────────────────────────────────────

    public List<Map<String, Object>> getExternalRestApiConnectionList(Map<String, Object> params) {
        String rawSearch = (String) params.get("search");
        if (rawSearch != null && !rawSearch.isBlank()) {
            params.put("search", "%" + rawSearch.trim() + "%");
        }
        String filterCompany = (String) params.get("filter_company_code");
        if (filterCompany != null) {
            params.put("filter_company_code", filterCompany);
        }
        String isActive = (String) params.get("is_active");
        if (isActive != null) params.put("is_active", isActive);
        String authType = (String) params.get("auth_type");
        if (authType != null) params.put("auth_type", authType);

        commonService.applyCompanyCodeFilter(params);
        List<Map<String, Object>> connections = sqlSession.selectList("externalRestApiConnection.getExternalRestApiConnectionList", params);

        for (Map<String, Object> conn : connections) {
            parseJsonFields(conn);
            decryptAuthConfig(conn);
        }
        return connections;
    }

    // ── 상세 조회 ──────────────────────────────────────────────────────────

    public Map<String, Object> getExternalRestApiConnectionInfo(Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        Map<String, Object> conn = sqlSession.selectOne("externalRestApiConnection.getExternalRestApiConnectionInfo", params);
        if (conn != null) {
            parseJsonFields(conn);
            decryptAuthConfig(conn);
        }
        return conn;
    }

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

    @Transactional
    public Map<String, Object> insertExternalRestApiConnection(Map<String, Object> params) {
        validateConnectionData(params);

        serializeJsonFields(params);
        encryptAuthConfig(params);

        if (params.get("timeout") == null) params.put("timeout", 30000);
        if (params.get("retry_count") == null) params.put("retry_count", 0);
        if (params.get("retry_delay") == null) params.put("retry_delay", 1000);
        if (params.get("is_active") == null) params.put("is_active", "Y");
        if (params.get("default_method") == null) params.put("default_method", "GET");
        if (params.get("save_to_history") == null) params.put("save_to_history", "N");

        sqlSession.insert("externalRestApiConnection.insertExternalRestApiConnection", params);
        log.info("REST API 연결 생성 성공: {}", params.get("connection_name"));

        return params;
    }

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

    @Transactional
    public Map<String, Object> updateExternalRestApiConnection(int id, Map<String, Object> params) {
        params.put("id", id);
        commonService.applyCompanyCodeFilter(params);

        Map<String, Object> existing = sqlSession.selectOne("externalRestApiConnection.getExternalRestApiConnectionInfo", params);
        if (existing == null) {
            return null;
        }

        serializeJsonFields(params);
        encryptAuthConfig(params);

        if (params.containsKey("default_body")) {
            params.put("has_default_body", "Y");
        }

        sqlSession.update("externalRestApiConnection.updateExternalRestApiConnection", params);
        log.info("REST API 연결 수정 성공: ID {}", id);

        return sqlSession.selectOne("externalRestApiConnection.getExternalRestApiConnectionInfo", Map.of("id", id));
    }

    // ── 삭제 ──────────────────────────────────────────────────────────────

    @Transactional
    public boolean deleteExternalRestApiConnection(int id, Map<String, Object> params) {
        params.put("id", id);
        commonService.applyCompanyCodeFilter(params);
        int deleted = sqlSession.delete("externalRestApiConnection.deleteExternalRestApiConnection", params);
        if (deleted > 0) {
            log.info("REST API 연결 삭제 성공: ID {}", id);
            return true;
        }
        return false;
    }

    // ── 연결 테스트 (테스트 데이터 기반) ────────────────────────────────────

    public Map<String, Object> testConnection(Map<String, Object> testRequest, String userCompanyCode) {
        long startTime = System.currentTimeMillis();
        try {
            String baseUrl = (String) testRequest.get("base_url");
            String endpoint = (String) testRequest.get("endpoint");
            String method = (String) testRequest.getOrDefault("method", "GET");
            String authType = (String) testRequest.getOrDefault("auth_type", "none");
            Object authConfigObj = testRequest.get("auth_config");
            Object headersObj = testRequest.get("headers");
            Object bodyObj = testRequest.get("body");
            int timeout = testRequest.get("timeout") != null
                    ? Integer.parseInt(testRequest.get("timeout").toString()) : 30000;

            String url = buildUrl(baseUrl, endpoint);

            HttpHeaders httpHeaders = new HttpHeaders();
            applyCustomHeaders(httpHeaders, headersObj);
            applyAuthHeaders(httpHeaders, authType, authConfigObj, url, userCompanyCode);

            if (bodyObj != null) {
                httpHeaders.setContentType(MediaType.APPLICATION_JSON);
            }

            String bodyStr = null;
            if (bodyObj != null) {
                bodyStr = bodyObj instanceof String ? (String) bodyObj : objectMapper.writeValueAsString(bodyObj);
            }

            HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
            RequestEntity<String> requestEntity = new RequestEntity<>(
                    bodyStr, httpHeaders, httpMethod, URI.create(url));

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

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

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

            Map<String, Object> result = new LinkedHashMap<>();
            result.put("success", success);
            result.put("message", success ? "연결 성공" : "연결 실패 (" + response.getStatusCode() + ")");
            result.put("response_time", responseTime);
            result.put("status_code", response.getStatusCode().value());
            result.put("response_data", responseData);
            return result;
        } catch (Exception e) {
            long responseTime = System.currentTimeMillis() - startTime;
            log.error("REST API 연결 테스트 오류:", e);
            Map<String, Object> result = new LinkedHashMap<>();
            result.put("success", false);
            result.put("message", "연결 실패");
            result.put("response_time", responseTime);
            result.put("error_details", e.getMessage());
            return result;
        }
    }

    // ── 연결 테스트 (ID 기반) ──────────────────────────────────────────────

    public Map<String, Object> testConnectionById(int id, String endpoint) {
        Map<String, Object> conn = sqlSession.selectOne("externalRestApiConnection.getExternalRestApiConnectionInfo", Map.of("id", id));
        if (conn == null) {
            return Map.of("success", false, "message", "연결을 찾을 수 없습니다.");
        }
        parseJsonFields(conn);
        decryptAuthConfig(conn);

        String effectiveEndpoint = endpoint != null ? endpoint
                : (String) conn.get("endpoint_path");

        Map<String, Object> testRequest = new LinkedHashMap<>();
        testRequest.put("base_url", conn.get("base_url"));
        testRequest.put("endpoint", effectiveEndpoint);
        testRequest.put("method", conn.getOrDefault("default_method", "GET"));
        testRequest.put("headers", conn.get("default_headers"));
        testRequest.put("body", conn.get("default_body"));
        testRequest.put("auth_type", conn.get("auth_type"));
        testRequest.put("auth_config", conn.get("auth_config"));
        testRequest.put("timeout", conn.get("timeout"));

        String companyCode = (String) conn.get("company_code");
        Map<String, Object> result = testConnection(testRequest, companyCode);

        try {
            Map<String, Object> updateParams = new HashMap<>();
            updateParams.put("id", id);
            updateParams.put("last_test_result", Boolean.TRUE.equals(result.get("success")) ? "Y" : "N");
            updateParams.put("last_test_message", result.get("message"));
            sqlSession.update("externalRestApiConnection.updateExternalRestApiConnectionTestResult", updateParams);
        } catch (Exception e) {
            log.warn("테스트 결과 저장 실패: {}", e.getMessage());
        }

        return result;
    }

    // ── 데이터 조회 (프록시) ────────────────────────────────────────────────

    public Map<String, Object> fetchData(int connectionId, String endpoint,
                                          String jsonPath, Map<String, Object> params) {
        commonService.applyCompanyCodeFilter(params);
        params.put("id", connectionId);
        Map<String, Object> conn = sqlSession.selectOne("externalRestApiConnection.getExternalRestApiConnectionInfo", params);
        if (conn == null) {
            return Map.of("success", false, "message", "REST API 연결을 찾을 수 없습니다.");
        }
        parseJsonFields(conn);
        decryptAuthConfig(conn);

        if (!"Y".equals(conn.get("is_active"))) {
            return Map.of("success", false, "message", "비활성화된 REST API 연결입니다.");
        }

        String effectiveEndpoint = endpoint != null ? endpoint
                : (String) conn.getOrDefault("endpoint_path", "");

        Map<String, Object> testRequest = new LinkedHashMap<>();
        testRequest.put("base_url", conn.get("base_url"));
        testRequest.put("endpoint", effectiveEndpoint);
        testRequest.put("method", conn.getOrDefault("default_method", "GET"));
        testRequest.put("headers", conn.get("default_headers"));
        testRequest.put("body", conn.get("default_body"));
        testRequest.put("auth_type", conn.get("auth_type"));
        testRequest.put("auth_config", conn.get("auth_config"));
        testRequest.put("timeout", conn.get("timeout"));

        String companyCode = (String) conn.get("company_code");
        Map<String, Object> testResult = testConnection(testRequest, companyCode);

        if (!Boolean.TRUE.equals(testResult.get("success"))) {
            return Map.of("success", false,
                    "message", testResult.getOrDefault("message", "REST API 호출에 실패했습니다."));
        }

        Object responseData = testResult.get("response_data");
        Object extractedData = extractByJsonPath(responseData, jsonPath);

        List<Object> dataArray;
        if (extractedData == null) {
            dataArray = responseData instanceof List ? (List<Object>) responseData
                    : (responseData != null ? List.of(responseData) : List.of());
        } else {
            dataArray = extractedData instanceof List ? (List<Object>) extractedData
                    : List.of(extractedData);
        }

        List<Map<String, Object>> columns = new ArrayList<>();
        if (!dataArray.isEmpty() && dataArray.get(0) instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> firstItem = (Map<String, Object>) dataArray.get(0);
            for (Map.Entry<String, Object> entry : firstItem.entrySet()) {
                Map<String, Object> col = new LinkedHashMap<>();
                col.put("column_name", entry.getKey());
                col.put("column_label", entry.getKey());
                col.put("data_type", entry.getValue() != null ? entry.getValue().getClass().getSimpleName() : "String");
                columns.add(col);
            }
        }

        Map<String, Object> connectionInfo = new LinkedHashMap<>();
        connectionInfo.put("connection_id", conn.get("id"));
        connectionInfo.put("connection_name", conn.get("connection_name"));
        connectionInfo.put("base_url", conn.get("base_url"));
        connectionInfo.put("endpoint", effectiveEndpoint);

        Map<String, Object> data = new LinkedHashMap<>();
        data.put("rows", dataArray);
        data.put("columns", columns);
        data.put("total", dataArray.size());
        data.put("connection_info", connectionInfo);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("success", true);
        result.put("data", data);
        result.put("message", dataArray.size() + "개의 데이터를 조회했습니다.");
        return result;
    }

    // ── Private helpers ────────────────────────────────────────────────────

    private String buildUrl(String baseUrl, String endpoint) {
        if (endpoint == null || endpoint.isBlank()) return baseUrl;
        if (endpoint.startsWith("/")) return baseUrl + endpoint;
        return baseUrl + "/" + endpoint;
    }

    @SuppressWarnings("unchecked")
    private void applyCustomHeaders(HttpHeaders httpHeaders, Object headersObj) {
        if (headersObj instanceof Map) {
            ((Map<String, Object>) headersObj).forEach((k, v) ->
                    httpHeaders.set(k, v != null ? v.toString() : ""));
        }
    }

    @SuppressWarnings("unchecked")
    private void applyAuthHeaders(HttpHeaders httpHeaders, String authType,
                                   Object authConfigObj, String url, String companyCode) {
        if (authConfigObj == null || "none".equals(authType)) return;

        Map<String, Object> authConfig;
        if (authConfigObj instanceof Map) {
            authConfig = (Map<String, Object>) authConfigObj;
        } else {
            return;
        }

        switch (authType) {
            case "bearer" -> {
                String token = (String) authConfig.get("token");
                if (token != null) httpHeaders.setBearerAuth(token);
            }
            case "basic" -> {
                String username = (String) authConfig.get("username");
                String password = (String) authConfig.get("password");
                if (username != null && password != null) {
                    httpHeaders.setBasicAuth(username, password);
                }
            }
            case "api-key" -> {
                String keyLocation = (String) authConfig.getOrDefault("key_location", "header");
                String keyName = (String) authConfig.get("key_name");
                String keyValue = (String) authConfig.get("key_value");
                if ("header".equals(keyLocation) && keyName != null && keyValue != null) {
                    httpHeaders.set(keyName, keyValue);
                }
            }
            default -> { }
        }
    }

    private Object extractByJsonPath(Object data, String jsonPath) {
        if (jsonPath == null || jsonPath.isBlank() || data == null) return data;
        String[] parts = jsonPath.split("\\.");
        Object current = data;
        for (String part : parts) {
            if (current instanceof Map) {
                current = ((Map<?, ?>) current).get(part);
            } else {
                return null;
            }
        }
        return current;
    }

    private void validateConnectionData(Map<String, Object> params) {
        String name = (String) params.get("connection_name");
        String baseUrl = (String) params.get("base_url");
        if (name == null || name.isBlank()) throw new IllegalArgumentException("연결명은 필수입니다.");
        if (baseUrl == null || baseUrl.isBlank()) throw new IllegalArgumentException("기본 URL은 필수입니다.");
    }

    @SuppressWarnings("unchecked")
    private void parseJsonFields(Map<String, Object> row) {
        parseJsonField(row, "default_headers");
        parseJsonField(row, "auth_config");
    }

    private void parseJsonField(Map<String, Object> row, String key) {
        Object val = row.get(key);
        if (val instanceof String str && !str.isBlank()) {
            try {
                row.put(key, objectMapper.readValue(str, Object.class));
            } catch (JsonProcessingException e) {
                log.warn("JSON 파싱 실패 ({}): {}", key, e.getMessage());
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void serializeJsonFields(Map<String, Object> params) {
        Object headers = params.get("default_headers");
        if (headers != null && !(headers instanceof String)) {
            try {
                params.put("default_headers", objectMapper.writeValueAsString(headers));
            } catch (JsonProcessingException e) {
                params.put("default_headers", "{}");
            }
        }
        if (params.get("default_headers") == null) {
            params.put("default_headers", "{}");
        }
    }

    @SuppressWarnings("unchecked")
    private void encryptAuthConfig(Map<String, Object> params) {
        Object authConfigObj = params.get("auth_config");
        if (authConfigObj == null) return;

        Map<String, Object> authConfig;
        if (authConfigObj instanceof Map) {
            authConfig = new LinkedHashMap<>((Map<String, Object>) authConfigObj);
        } else if (authConfigObj instanceof String str) {
            try {
                authConfig = objectMapper.readValue(str, Map.class);
            } catch (JsonProcessingException e) {
                return;
            }
        } else {
            return;
        }

        encryptField(authConfig, "key_value");
        encryptField(authConfig, "token");
        encryptField(authConfig, "password");
        encryptField(authConfig, "client_secret");

        try {
            params.put("auth_config", objectMapper.writeValueAsString(authConfig));
        } catch (JsonProcessingException e) {
            log.error("authConfig 직렬화 실패", e);
        }
    }

    @SuppressWarnings("unchecked")
    private void decryptAuthConfig(Map<String, Object> row) {
        Object authConfigObj = row.get("auth_config");
        if (authConfigObj == null) return;

        Map<String, Object> authConfig;
        if (authConfigObj instanceof Map) {
            authConfig = new LinkedHashMap<>((Map<String, Object>) authConfigObj);
        } else {
            return;
        }

        try {
            decryptField(authConfig, "key_value");
            decryptField(authConfig, "token");
            decryptField(authConfig, "password");
            decryptField(authConfig, "client_secret");
            row.put("auth_config", authConfig);
        } catch (Exception e) {
            log.warn("민감 정보 복호화 실패 (암호화되지 않은 데이터일 수 있음)");
        }
    }

    private void encryptField(Map<String, Object> map, String key) {
        Object val = map.get(key);
        if (val instanceof String str && !str.isBlank()) {
            map.put(key, encrypt(str));
        }
    }

    private void decryptField(Map<String, Object> map, String key) {
        Object val = map.get(key);
        if (val instanceof String str && !str.isBlank()) {
            map.put(key, decrypt(str));
        }
    }

    private String encrypt(String plainText) {
        try {
            byte[] iv = new byte[IV_LENGTH];
            new SecureRandom().nextBytes(iv);
            byte[] keyBytes = deriveKey();

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE,
                    new SecretKeySpec(keyBytes, "AES"),
                    new GCMParameterSpec(GCM_TAG_LENGTH, iv));

            byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
            return HexFormat.of().formatHex(iv)
                    + ":" + HexFormat.of().formatHex(Arrays.copyOfRange(encrypted, encrypted.length - 16, encrypted.length))
                    + ":" + HexFormat.of().formatHex(Arrays.copyOf(encrypted, encrypted.length - 16));
        } catch (Exception e) {
            log.error("암호화 실패", e);
            return plainText;
        }
    }

    private String decrypt(String cipherText) {
        String[] parts = cipherText.split(":");
        if (parts.length != 3) return cipherText;

        try {
            byte[] iv = HexFormat.of().parseHex(parts[0]);
            byte[] authTag = HexFormat.of().parseHex(parts[1]);
            byte[] encryptedText = HexFormat.of().parseHex(parts[2]);
            byte[] keyBytes = deriveKey();

            byte[] combined = new byte[encryptedText.length + authTag.length];
            System.arraycopy(encryptedText, 0, combined, 0, encryptedText.length);
            System.arraycopy(authTag, 0, combined, encryptedText.length, authTag.length);

            Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE,
                    new SecretKeySpec(keyBytes, "AES"),
                    new GCMParameterSpec(GCM_TAG_LENGTH, iv));

            return new String(cipher.doFinal(combined), StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.warn("복호화 실패 (평문 데이터 반환)");
            return cipherText;
        }
    }

    private byte[] deriveKey() throws Exception {
        KeySpec spec = new PBEKeySpec(encryptionKey.toCharArray(), "salt".getBytes(StandardCharsets.UTF_8), 65536, 256);
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        return factory.generateSecret(spec).getEncoded();
    }
}
