package com.erp.provisioning;

import com.erp.tenant.DbContextHolder;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Service;

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

/**
 * 회사 관리 화면(/admin/sysMng/subdomainList)의 라이프사이클 액션 감사 로그.
 *
 * 테이블: COMPANY_AUDIT_LOG (메타 DB). 테넌트 DB 내부 CRUD 로그용 {@link com.erp.service.AuditLogService}
 * 와는 스코프가 다름. 이 서비스는 SUPER_ADMIN 전용.
 *
 * action 상수:
 *   COMPANY_CREATE            — 프로비저닝 성공
 *   COMPANY_CREATE_FAILED     — 프로비저닝 실패 (자동 롤백 완료 후)
 *   COMPANY_DEACTIVATE        — DB_STATUS=suspended
 *   COMPANY_REACTIVATE        — suspended→active 복귀
 *   COMPANY_DELETE            — DROP DATABASE + row 삭제
 *   ADMIN_PASSWORD_RESET      — 관리자 비번 재설정
 *   TEMPLATES_RECOPY          — 템플릿 재복제
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CompanyAuditLogService {

    public static final String ACTION_CREATE          = "COMPANY_CREATE";
    public static final String ACTION_CREATE_FAILED   = "COMPANY_CREATE_FAILED";
    public static final String ACTION_DEACTIVATE      = "COMPANY_DEACTIVATE";
    public static final String ACTION_REACTIVATE      = "COMPANY_REACTIVATE";
    public static final String ACTION_DELETE          = "COMPANY_DELETE";
    public static final String ACTION_PW_RESET        = "ADMIN_PASSWORD_RESET";
    public static final String ACTION_RECOPY          = "TEMPLATES_RECOPY";

    // cross-tenant write 감사 액션
    public static final String ACTION_CT_USER_CREATE    = "CROSS_TENANT_USER_CREATE";
    public static final String ACTION_CT_USER_DELETE    = "CROSS_TENANT_USER_DELETE";
    public static final String ACTION_CT_PW_RESET       = "CROSS_TENANT_PASSWORD_RESET";
    public static final String ACTION_CT_ROLE_UPDATE    = "CROSS_TENANT_ROLE_UPDATE";

    private final SqlSession sqlSession;
    private final ObjectMapper objectMapper;

    /** 성공 기록. */
    public void log(String companyCode, String actorUserId, String action, String target, Map<String, Object> details) {
        doLog(companyCode, actorUserId, action, target, details, true, null);
    }

    /** 실패 기록. */
    public void logFailure(String companyCode, String actorUserId, String action, String target,
                           Map<String, Object> details, String errorMessage) {
        doLog(companyCode, actorUserId, action, target, details, false, errorMessage);
    }

    private void doLog(String companyCode, String actorUserId, String action, String target,
                       Map<String, Object> details, boolean success, String errorMessage) {
        // 감사 로그는 항상 메타 DB 에 기록. tenant 컨텍스트 영향 받지 않도록 강제.
        String prevCtx = DbContextHolder.get();
        DbContextHolder.setMeta();
        try {
            Map<String, Object> params = new HashMap<>();
            params.put("company_code", companyCode);
            params.put("actor_user_id", actorUserId);
            params.put("action", action);
            params.put("target", target);
            params.put("details_json", details == null ? null : toJson(details));
            params.put("success", success);
            params.put("error_message", errorMessage);
            sqlSession.insert("provisioning.insertAuditLog", params);
        } catch (Exception e) {
            // 감사 로그 실패가 본 작업을 깨면 안 됨.
            log.error("[CompanyAudit] log failed: company={} action={} err={}", companyCode, action, e.getMessage());
        } finally {
            if (prevCtx == null) {
                DbContextHolder.clear();
            } else {
                DbContextHolder.set(prevCtx);
            }
        }
    }

    /** 특정 회사 감사 로그 (최신순, 페이지네이션). */
    public Map<String, Object> listByCompany(String companyCode, int page, int limit) {
        if (page < 1) page = 1;
        if (limit < 1 || limit > 200) limit = 50;
        int offset = (page - 1) * limit;
        Map<String, Object> p = new HashMap<>();
        p.put("company_code", companyCode);
        p.put("limit", limit);
        p.put("offset", offset);

        Number totalNum = sqlSession.selectOne("provisioning.countAuditLog", p);
        List<Map<String, Object>> rows = sqlSession.selectList("provisioning.selectAuditLog", p);
        hydrateDetails(rows);

        Map<String, Object> out = new LinkedHashMap<>();
        out.put("data", rows);
        out.put("total", totalNum == null ? 0 : totalNum.intValue());
        out.put("page", page);
        out.put("limit", limit);
        return out;
    }

    /** 전체 감사 로그 (헤더 버튼용). */
    public Map<String, Object> listAll(int page, int limit, String action) {
        if (page < 1) page = 1;
        if (limit < 1 || limit > 200) limit = 50;
        int offset = (page - 1) * limit;
        Map<String, Object> p = new HashMap<>();
        p.put("action", action);
        p.put("limit", limit);
        p.put("offset", offset);

        Number totalNum = sqlSession.selectOne("provisioning.countAuditLog", p);
        List<Map<String, Object>> rows = sqlSession.selectList("provisioning.selectAuditLog", p);
        hydrateDetails(rows);

        Map<String, Object> out = new LinkedHashMap<>();
        out.put("data", rows);
        out.put("total", totalNum == null ? 0 : totalNum.intValue());
        out.put("page", page);
        out.put("limit", limit);
        return out;
    }

    private String toJson(Map<String, Object> details) {
        try {
            return objectMapper.writeValueAsString(details);
        } catch (Exception e) {
            log.warn("[CompanyAudit] JSON serialize failed: {}", e.getMessage());
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private void hydrateDetails(List<Map<String, Object>> rows) {
        for (Map<String, Object> r : rows) {
            Object raw = r.get("details");
            if (raw == null) continue;
            try {
                Map<String, Object> parsed = objectMapper.readValue(raw.toString(), Map.class);
                r.put("details", parsed);
            } catch (Exception e) {
                // 파싱 실패해도 원본 문자열 유지
            }
        }
    }
}
