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.stereotype.Service;

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

@Service
@Slf4j
public class AuditLogService extends BaseService {

    @Autowired
    private ObjectMapper objectMapper;

    private static final String SECURITY_MASK = "(보안 항목 - 값 비공개)";
    private static final List<String> SECURED_TABLES = List.of("table_type_columns");

    /**
     * 감사 로그 목록 조회 (페이지네이션 + 보안 마스킹)
     */
    public Map<String, Object> queryLogs(Map<String, Object> filters, boolean isSuperAdmin) {
        int page = toInt(filters.get("page"), 1);
        int limit = toInt(filters.get("limit"), 50);
        int offset = (page - 1) * limit;

        Map<String, Object> params = new HashMap<>(filters);
        params.put("limit", limit);
        params.put("offset", offset);

        Number totalNum = sqlSession.selectOne("auditLog.selectAuditLogsCount", params);
        int total = totalNum != null ? totalNum.intValue() : 0;
        List<Map<String, Object>> data = sqlSession.selectList("auditLog.selectAuditLogs", params);

        // changes 필드 처리 (JSONB → Java Map) + 보안 마스킹
        processChanges(data, isSuperAdmin);

        Map<String, Object> result = new HashMap<>();
        result.put("data", data);
        result.put("total", total);
        return result;
    }

    /**
     * 통계 조회 (일별 / 리소스타입 / 액션 / 상위사용자)
     */
    public Map<String, Object> getStats(String companyCode, int days) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("days", days);

        Map<String, Object> result = new HashMap<>();
        result.put("daily_counts", sqlSession.selectList("auditLog.selectDailyCounts", params));
        result.put("resource_type_counts", sqlSession.selectList("auditLog.selectResourceTypeCounts", params));
        result.put("action_counts", sqlSession.selectList("auditLog.selectActionCounts", params));
        result.put("top_users", sqlSession.selectList("auditLog.selectTopUsers", params));
        return result;
    }

    /**
     * 감사 로그가 있는 활성 사용자 목록
     */
    public List<Map<String, Object>> getAuditLogUsers(String companyCode, boolean isSuperAdmin) {
        Map<String, Object> params = new HashMap<>();
        params.put("company_code", companyCode);
        params.put("exclude_wildcard", !isSuperAdmin);
        return sqlSession.selectList("auditLog.selectAuditLogUsers", params);
    }

    /**
     * 감사 로그 1건 기록.
     *
     * PROCESSOR 처리 (대무 추적):
     * - processor_id 미지정 → user_id 로 채움 (평시 = 동일 = 본인 처리)
     * - processor_id 가 user_id 와 다르고 processor_name 미지정 → USER_INFO 에서 lookup
     *   (대무 이벤트만 USER_INFO 단건 조회 1회 — 평시는 추가 DB 호출 없음)
     */
    public void insertAuditLog(Map<String, Object> params) {
        // changes가 Map이면 JSON 문자열로 직렬화
        Object changes = params.get("changes");
        if (changes instanceof Map || changes instanceof List) {
            try {
                params.put("changes", objectMapper.writeValueAsString(changes));
            } catch (Exception e) {
                log.warn("changes 직렬화 실패, null로 처리: {}", e.getMessage());
                params.put("changes", null);
            }
        }

        Object processorId = params.get("processor_id");
        Object userId      = params.get("user_id");
        if (processorId == null) {
            params.put("processor_id", userId);
            if (params.get("processor_name") == null) {
                params.put("processor_name", params.get("user_name"));
            }
        } else if (!processorId.equals(userId) && params.get("processor_name") == null) {
            try {
                Map<String, Object> p = new HashMap<>();
                p.put("user_id",      processorId);
                p.put("company_code", params.get("company_code"));
                String name = sqlSession.selectOne("auditLog.selectUserNameById", p);
                params.put("processor_name", name);
            } catch (Exception e) {
                log.warn("processor_name lookup 실패 (processor_id={}): {}", processorId, e.getMessage());
            }
        }

        sqlSession.insert("auditLog.insertAuditLog", params);
    }

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

    /**
     * JSONB 컬럼(changes) 역직렬화 및 보안 마스킹 처리
     */
    private void processChanges(List<Map<String, Object>> entries, boolean isSuperAdmin) {
        for (Map<String, Object> entry : entries) {
            Object raw = entry.get("changes");
            if (raw == null) continue;

            String json = raw.toString(); // PGobject.toString() = JSON 문자열
            try {
                Map<String, Object> changes = objectMapper.readValue(
                        json, new TypeReference<Map<String, Object>>() {});

                if (!isSuperAdmin) {
                    String tableName = (String) entry.get("table_name");
                    if (tableName != null && SECURED_TABLES.contains(tableName)) {
                        maskMapValues(changes, "before");
                        maskMapValues(changes, "after");
                    }
                }
                entry.put("changes", changes);
            } catch (Exception e) {
                log.warn("changes JSON 파싱 실패, 원본 유지: {}", e.getMessage());
                entry.put("changes", json);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void maskMapValues(Map<String, Object> changes, String key) {
        Object section = changes.get(key);
        if (!(section instanceof Map)) return;
        Map<String, Object> sectionMap = (Map<String, Object>) section;
        sectionMap.replaceAll((k, v) -> SECURITY_MASK);
    }

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