package com.erp.service;

import com.erp.common.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.postgresql.util.PSQLException;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@Slf4j
public class TableHistoryService extends BaseService {

    private static final String NS = "tableHistory.";

    // ─── Public API ────────────────────────────────────────────────

    public Map<String, Object> checkHistoryTableExists(Map<String, Object> params) {
        String tableName    = sanitizeTableName((String) params.get("table_name"));
        String logTableName = tableName + "_log";
        params.put("log_table_name", logTableName);

        Map<String, Object> row     = sqlSession.selectOne(NS + "checkHistoryTableExists", params);
        boolean             exists  = Boolean.TRUE.equals(row != null ? row.get("exists") : Boolean.FALSE);
        String              message = exists ? "이력 테이블이 존재합니다." : "이력 테이블이 존재하지 않습니다.";

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("table_name",       tableName);
        result.put("log_table_name",    logTableName);
        result.put("exists",          exists);
        result.put("history_enabled",  exists);
        result.put("_message",        message);
        return result;
    }

    public List<Map<String, Object>> getTableHistorySummary(Map<String, Object> params) {
        prepareLogTableName(params);
        return withTableNotFound(() -> sqlSession.selectList(NS + "selectTableHistorySummary", params));
    }

    public Map<String, Object> getAllTableHistory(Map<String, Object> params) {
        prepareLogTableName(params);
        prepareFilterParams(params);
        applyPagination(params);

        int limit  = (int) params.get("limit");
        int offset = (int) params.get("offset");

        List<Map<String, Object>> records = withTableNotFound(() -> sqlSession.selectList(NS + "selectAllTableHistory", params));
        Integer totalObj = withTableNotFound(() -> sqlSession.selectOne(NS + "countAllTableHistory", params));
        int total = totalObj != null ? totalObj : 0;

        return buildPaginatedResult(records, total, limit, offset, (offset + limit) < total);
    }

    public Map<String, Object> getRecordHistory(Map<String, Object> params) {
        prepareLogTableName(params);
        prepareFilterParams(params);
        applyPagination(params);

        int limit  = (int) params.get("limit");
        int offset = (int) params.get("offset");

        List<Map<String, Object>> records = withTableNotFound(() -> sqlSession.selectList(NS + "selectRecordHistory", params));
        Integer totalObj = withTableNotFound(() -> sqlSession.selectOne(NS + "countRecordHistory", params));
        int total = totalObj != null ? totalObj : 0;

        return buildPaginatedResult(records, total, limit, offset, (offset + records.size()) < total);
    }

    public List<Map<String, Object>> getRecordTimeline(Map<String, Object> params) {
        prepareLogTableName(params);
        return withTableNotFound(() -> sqlSession.selectList(NS + "selectRecordTimeline", params));
    }

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

    private void prepareLogTableName(Map<String, Object> params) {
        String tableName = sanitizeTableName((String) params.get("table_name"));
        params.put("table_name",    tableName);
        params.put("log_table_name", tableName + "_log");
    }

    private void prepareFilterParams(Map<String, Object> params) {
        Object changedBy = params.get("changed_by");
        if (changedBy != null && !changedBy.toString().isBlank()) {
            params.put("changed_by_like", "%" + changedBy + "%");
        }
    }

    private void applyPagination(Map<String, Object> params) {
        params.put("limit",  parseIntOrDefault(params.get("limit"),  50));
        params.put("offset", parseIntOrDefault(params.get("offset"), 0));
    }

    private Map<String, Object> buildPaginatedResult(
            List<Map<String, Object>> records, int total, int limit, int offset, boolean hasMore) {
        Map<String, Object> pagination = new LinkedHashMap<>();
        pagination.put("total",   total);
        pagination.put("limit",   limit);
        pagination.put("offset",  offset);
        pagination.put("has_more", hasMore);

        Map<String, Object> result = new LinkedHashMap<>();
        result.put("records",    records);
        result.put("pagination", pagination);
        return result;
    }

    private String sanitizeTableName(String tableName) {
        if (tableName == null || tableName.isBlank()) {
            throw new IllegalArgumentException("테이블명은 필수입니다.");
        }
        String sanitized = tableName.replaceAll("[^a-zA-Z0-9_]", "");
        if (sanitized.isEmpty()) {
            throw new IllegalArgumentException("유효하지 않은 테이블명입니다.");
        }
        return sanitized;
    }

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

    private <T> T withTableNotFound(java.util.function.Supplier<T> supplier) {
        try {
            return supplier.get();
        } catch (RuntimeException e) {
            if (isTableNotFoundError(e)) {
                throw new RuntimeException("TABLE_NOT_FOUND");
            }
            throw e;
        }
    }

    private boolean isTableNotFoundError(Throwable e) {
        Throwable cause = e;
        while (cause != null) {
            if (cause instanceof PSQLException psql && "42P01".equals(psql.getSQLState())) {
                return true;
            }
            cause = cause.getCause();
        }
        return false;
    }
}
