package com.erp.service;

import com.erp.common.BaseService;
import com.erp.util.CredentialEncryption;
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.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;

/**
 * 플로우 전용 외부 DB 연결 관리 서비스
 * Node.js FlowExternalDbConnectionService 와 동일한 동작
 *
 * - 암호화: AES-256-GCM + SCrypt 키 유도 (CredentialEncryption)
 * - 연결 테스트/테이블조회 : DriverManager 직접 연결 (JdbcTemplate 는 메인 DB 전용)
 */
@Service
@Slf4j
public class FlowExternalDbConnectionService extends BaseService {

    @Autowired
    private ObjectMapper objectMapper;
    private final CredentialEncryption encryption;

    public FlowExternalDbConnectionService(
            @Value("${app.flow-db-secret:flow-external-db-secret-key-2025}") String secretKey) {
        this.encryption = new CredentialEncryption(secretKey);
    }

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

    public List<Map<String, Object>> findAll(boolean activeOnly) {
        Map<String, Object> params = new HashMap<>();
        params.put("active_only", activeOnly);
        List<Map<String, Object>> rows = sqlSession.selectList("flowExternalDbConnection.selectAll", params);
        rows.forEach(this::parseJsonFields);
        return rows;
    }

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

    public Map<String, Object> findById(int id) {
        Map<String, Object> row = sqlSession.selectOne("flowExternalDbConnection.selectById", Map.of("id", id));
        if (row != null) parseJsonFields(row);
        return row;
    }

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

    @Transactional
    public Map<String, Object> create(Map<String, Object> request, String userId) {
        Map<String, Object> params = new HashMap<>(request);

        // 필수 필드 이름 정규화 (snake_case → camelCase)
        normalize(params);

        // 비밀번호 암호화
        String plainPassword = (String) params.get("password");
        params.put("password_encrypted", encryption.encrypt(plainPassword));
        params.remove("password");

        // connectionOptions → JSON string
        if (params.get("connection_options") != null && !(params.get("connection_options") instanceof String)) {
            params.put("connection_options", toJson(params.get("connection_options")));
        }

        params.putIfAbsent("ssl_enabled", false);
        params.put("created_by", userId);
        params.put("updated_by", userId);

        sqlSession.insert("flowExternalDbConnection.insert", params);

        // RETURNING * 대신 insert 후 조회
        int newId = ((Number) params.get("id")).intValue();
        log.info("외부 DB 연결 생성: id={}, name={}", newId, params.get("name"));
        return findById(newId);
    }

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

    @Transactional
    public Map<String, Object> update(int id, Map<String, Object> request, String userId) {
        Map<String, Object> params = new HashMap<>(request);
        normalize(params);
        params.put("id",        id);
        params.put("updated_by", userId);

        // 새 비밀번호 제공 시 암호화
        if (params.get("password") != null) {
            params.put("password_encrypted", encryption.encrypt((String) params.get("password")));
            params.remove("password");
        }

        // connectionOptions → JSON string
        if (params.get("connection_options") != null && !(params.get("connection_options") instanceof String)) {
            params.put("connection_options", toJson(params.get("connection_options")));
        }

        int affected = sqlSession.update("flowExternalDbConnection.update", params);
        if (affected == 0) return null;

        log.info("외부 DB 연결 수정: id={}", id);
        return findById(id);
    }

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

    @Transactional
    public boolean delete(int id) {
        int affected = sqlSession.delete("flowExternalDbConnection.delete", Map.of("id", id));
        if (affected == 0) return false;
        log.info("외부 DB 연결 삭제: id={}", id);
        return true;
    }

    // ── 연결 테스트 ────────────────────────────────────────────────

    public Map<String, Object> testConnection(int id) {
        Map<String, Object> conn = findById(id);
        if (conn == null) return result(false, "연결 정보를 찾을 수 없습니다.");

        try (Connection jdbc = openJdbc(conn)) {
            jdbc.prepareStatement("SELECT 1").execute();
            return result(true, "연결 성공");
        } catch (Exception e) {
            return result(false, e.getMessage());
        }
    }

    // ── 테이블 목록 조회 ───────────────────────────────────────────

    public Map<String, Object> getTables(int id) {
        Map<String, Object> conn = findById(id);
        if (conn == null) return result(false, "연결 정보를 찾을 수 없습니다.");

        String dbType = strOf(conn.get("db_type"));
        String query;
        switch (dbType) {
            case "postgresql":
                query = "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename";
                break;
            case "mysql":
                query = "SELECT table_name AS tablename FROM information_schema.tables WHERE table_schema = DATABASE() ORDER BY table_name";
                break;
            default:
                return result(false, "지원하지 않는 DB 타입: " + dbType);
        }

        try (Connection jdbc = openJdbc(conn);
             PreparedStatement ps = jdbc.prepareStatement(query);
             ResultSet rs = ps.executeQuery()) {

            List<String> tables = new ArrayList<>();
            while (rs.next()) tables.add(rs.getString(1));

            Map<String, Object> out = result(true, null);
            out.put("data", tables);
            return out;
        } catch (Exception e) {
            return result(false, e.getMessage());
        }
    }

    // ── 컬럼 목록 조회 ─────────────────────────────────────────────

    public Map<String, Object> getTableColumns(int id, String tableName) {
        Map<String, Object> conn = findById(id);
        if (conn == null) return result(false, "연결 정보를 찾을 수 없습니다.");

        String dbType = strOf(conn.get("db_type"));
        String sql;
        Object[] args;
        switch (dbType) {
            case "postgresql":
                sql  = "SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ? ORDER BY ordinal_position";
                args = new Object[]{tableName};
                break;
            case "mysql":
                sql  = "SELECT column_name, data_type FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? ORDER BY ordinal_position";
                args = new Object[]{tableName};
                break;
            default:
                return result(false, "지원하지 않는 DB 타입: " + dbType);
        }

        try (Connection jdbc = openJdbc(conn);
             PreparedStatement ps = jdbc.prepareStatement(sql)) {

            ps.setObject(1, args[0]);
            List<Map<String, Object>> cols = new ArrayList<>();
            try (ResultSet rs = ps.executeQuery()) {
                while (rs.next()) {
                    Map<String, Object> col = new LinkedHashMap<>();
                    col.put("column_name", rs.getString("column_name"));
                    col.put("data_type",   rs.getString("data_type"));
                    cols.add(col);
                }
            }

            Map<String, Object> out = result(true, null);
            out.put("data", cols);
            return out;
        } catch (Exception e) {
            return result(false, e.getMessage());
        }
    }

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

    /** DB row의 JSONB 컬럼(connection_options) 파싱 */
    private void parseJsonFields(Map<String, Object> row) {
        Object opts = row.get("connection_options");
        if (opts instanceof String) {
            try {
                row.put("connection_options", objectMapper.readValue((String) opts, Object.class));
            } catch (Exception ignored) {}
        }
    }

    /** snake_case 요청 필드를 camelCase params 로 정규화 */
    private void normalize(Map<String, Object> p) {
        mapField(p, "db_type",         "db_type");
        mapField(p, "database_name",   "database_name");
        mapField(p, "ssl_enabled",     "ssl_enabled");
        mapField(p, "connection_options", "connection_options");
        mapField(p, "is_active",       "is_active");
        mapField(p, "created_by",      "created_by");
        mapField(p, "updated_by",      "updated_by");
    }

    private void mapField(Map<String, Object> p, String from, String to) {
        if (p.get(to) == null && p.get(from) != null) p.put(to, p.get(from));
    }

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

    private String strOf(Object o) { return o == null ? "" : o.toString(); }

    private String toJson(Object obj) {
        try { return objectMapper.writeValueAsString(obj); }
        catch (JsonProcessingException e) { return "{}"; }
    }

    /** 외부 DB JDBC 연결 생성 (PostgreSQL, MySQL 지원) */
    private Connection openJdbc(Map<String, Object> conn) throws Exception {
        String dbType  = strOf(conn.get("db_type"));
        String host    = strOf(conn.get("host"));
        int    port    = ((Number) conn.get("port")).intValue();
        String dbName  = strOf(conn.get("database_name"));
        String user    = strOf(conn.get("username"));
        String encPwd  = strOf(conn.get("password_encrypted"));
        String password;
        try { password = encryption.decrypt(encPwd); }
        catch (Exception e) { password = encPwd; /* fallback */ }

        boolean ssl = Boolean.TRUE.equals(conn.get("ssl_enabled"));

        String jdbcUrl;
        switch (dbType) {
            case "postgresql":
                jdbcUrl = String.format("jdbc:postgresql://%s:%d/%s?sslmode=%s",
                        host, port, dbName, ssl ? "require" : "disable");
                Class.forName("org.postgresql.Driver");
                break;
            case "mysql":
                jdbcUrl = String.format("jdbc:mysql://%s:%d/%s?useSSL=%s&serverTimezone=UTC",
                        host, port, dbName, ssl);
                Class.forName("com.mysql.cj.jdbc.Driver");
                break;
            default:
                throw new UnsupportedOperationException("지원하지 않는 DB 타입: " + dbType);
        }

        return DriverManager.getConnection(jdbcUrl, user, password);
    }
}
