package com.erp.service;

import com.erp.common.BaseService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Service;

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

/**
 * 대무자(代務者) 관리 서비스.
 *
 * Spec: .omc/specs/deep-dive-user-substitute-management.md
 * Plan: .omc/plans/autopilot-impl.md (T3)
 *
 * 핵심 규칙:
 * - 관리자만 위임 지정/수정/해지. 본인 self-위임 불가.
 * - 종료일 필수, 시작일 옵션 (비우면 즉시).
 * - 같은 (COMPANY, ORIGINAL, PROXY) 쌍의 활성 기간 겹침 금지 (DB EXCLUDE + 사전 검증).
 * - 같은 회사 사용자끼리만. SUPER_ADMIN 은 대무자로 지정 불가.
 */
@Service
@Slf4j
public class SubstituteService extends BaseService {

    private static final String NS = "substitute.";

    // ─────────────────────────────────────────────────────────────
    // 조회
    // ─────────────────────────────────────────────────────────────

    public Map<String, Object> getSubstituteList(Map<String, Object> params) {
        requireAdmin(params);

        List<Map<String, Object>> list = sqlSession.selectList(NS + "selectSubstituteList", params);
        Integer total = sqlSession.selectOne(NS + "selectSubstituteListCnt", params);

        Map<String, Object> result = new HashMap<>();
        result.put("list", list);
        result.put("total", total == null ? 0 : total);
        return result;
    }

    /**
     * ProfileModal read-only: 내가 위임한(proxying_for_me) + 나를 대무 중인(my_proxies) 두 방향 한 번에.
     * 결과를 Java 단에서 partition.
     */
    public Map<String, Object> getMySubstitutes(Map<String, Object> params) {
        if (params.get("user_id") == null) {
            throw new IllegalArgumentException("user_id 가 필요합니다.");
        }

        List<Map<String, Object>> rows = sqlSession.selectList(NS + "selectMySubstitutes", params);

        List<Map<String, Object>> proxyingForMe = new ArrayList<>();
        List<Map<String, Object>> myProxies = new ArrayList<>();
        for (Map<String, Object> row : rows) {
            Object relation = row.get("relation");
            if ("proxying_for_me".equals(relation)) {
                proxyingForMe.add(row);
            } else if ("my_proxies".equals(relation)) {
                myProxies.add(row);
            }
        }

        Map<String, Object> result = new HashMap<>();
        result.put("proxying_for_me", proxyingForMe);
        result.put("my_proxies", myProxies);
        return result;
    }

    /**
     * SubstituteContextFilter 핫 패스. 트랜잭션 없이 가볍게.
     * 반환: B 가 현재 대무 중인 A 의 ID 목록 (없으면 빈 리스트).
     */
    public List<String> getActiveOriginalUserIds(String proxyUserId, String companyCode) {
        if (proxyUserId == null || companyCode == null || "*".equals(companyCode)) {
            return List.of();
        }
        Map<String, Object> p = new HashMap<>();
        p.put("proxy_user_id", proxyUserId);
        p.put("company_code", companyCode);
        List<String> ids = sqlSession.selectList(NS + "selectActiveOriginalUserIds", p);
        return ids == null ? List.of() : ids;
    }

    public Map<String, Object> getSubstituteInfo(Map<String, Object> params) {
        Map<String, Object> row = sqlSession.selectOne(NS + "selectSubstituteInfo", params);
        if (row == null) {
            throw new IllegalArgumentException("대무 설정을 찾을 수 없습니다.");
        }
        return row;
    }

    /**
     * ApprovalService 어댑터: B 가 A 의 대무자로 활성 상태인지 검증.
     * 결재 처리 중 호출.
     */
    public Map<String, Object> getActiveProxyForLine(Map<String, Object> params) {
        return sqlSession.selectOne(NS + "selectActiveProxyForLine", params);
    }

    public int checkOverlap(Map<String, Object> params) {
        Integer cnt = sqlSession.selectOne(NS + "countOverlap", params);
        return cnt == null ? 0 : cnt;
    }

    // ─────────────────────────────────────────────────────────────
    // 변경
    // ─────────────────────────────────────────────────────────────

    public Map<String, Object> insertSubstitute(Map<String, Object> params) {
        requireAdmin(params);
        validateInsertParams(params);

        sqlSession.insert(NS + "insertSubstitute", params);

        Map<String, Object> info = new HashMap<>();
        info.put("substitute_id", params.get("substitute_id"));
        info.put("company_code", params.get("company_code"));
        return getSubstituteInfo(info);
    }

    public Map<String, Object> updateSubstitute(Map<String, Object> params) {
        requireAdmin(params);

        Map<String, Object> existing = getSubstituteInfo(params);

        // 변경되는 사용자 ID 가 있으면 회사 소속 + SUPER_ADMIN 검증
        Object newProxy = params.get("proxy_user_id");
        if (newProxy != null && !newProxy.equals(existing.get("proxy_user_id"))) {
            validateUserInCompany((String) newProxy, (String) params.get("company_code"), "proxy");
            rejectSuperAdminAsProxy((String) newProxy);
        }

        // 기간/대무자 변경 시 겹침 재검증
        if (params.get("start_date") != null || params.get("end_date") != null
                || params.get("clear_start_date") != null || newProxy != null) {
            Map<String, Object> overlapParams = new HashMap<>();
            overlapParams.put("company_code", params.get("company_code"));
            overlapParams.put("original_user_id", existing.get("original_user_id"));
            overlapParams.put("proxy_user_id",
                    newProxy != null ? newProxy : existing.get("proxy_user_id"));
            overlapParams.put("start_date",
                    Boolean.TRUE.equals(params.get("clear_start_date")) ? null
                            : (params.get("start_date") != null ? params.get("start_date")
                                                                : existing.get("start_date")));
            overlapParams.put("end_date",
                    params.get("end_date") != null ? params.get("end_date")
                                                   : existing.get("end_date"));
            overlapParams.put("exclude_substitute_id", params.get("substitute_id"));
            if (checkOverlap(overlapParams) > 0) {
                throw new IllegalArgumentException("같은 대상-대무자 쌍의 활성 기간이 겹칩니다.");
            }
        }

        int updated = sqlSession.update(NS + "updateSubstitute", params);
        if (updated == 0) {
            throw new IllegalArgumentException("대무 설정 수정에 실패했습니다.");
        }
        return getSubstituteInfo(params);
    }

    public void deleteSubstitute(Map<String, Object> params) {
        requireAdmin(params);
        getSubstituteInfo(params); // 존재 확인
        sqlSession.delete(NS + "deleteSubstitute", params);
    }

    // ─────────────────────────────────────────────────────────────
    // 검증
    // ─────────────────────────────────────────────────────────────

    private void validateInsertParams(Map<String, Object> params) {
        String companyCode = (String) params.get("company_code");
        String original = (String) params.get("original_user_id");
        String proxy = (String) params.get("proxy_user_id");
        Object endDate = params.get("end_date");

        if (companyCode == null || companyCode.isBlank()) {
            throw new IllegalArgumentException("회사 코드가 필요합니다.");
        }
        if (original == null || original.isBlank()) {
            throw new IllegalArgumentException("위임자(대상 사용자) 가 필요합니다.");
        }
        if (proxy == null || proxy.isBlank()) {
            throw new IllegalArgumentException("대무자가 필요합니다.");
        }
        if (original.equals(proxy)) {
            throw new IllegalArgumentException("본인을 자기 대무자로 지정할 수 없습니다.");
        }
        if (endDate == null || (endDate instanceof String && ((String) endDate).isBlank())) {
            throw new IllegalArgumentException("종료일은 필수입니다 (무기한 대무 금지).");
        }

        // B3: 같은 회사 소속 검증
        validateUserInCompany(original, companyCode, "original");
        validateUserInCompany(proxy, companyCode, "proxy");

        // SUPER_ADMIN 을 대무자로 지정 금지
        rejectSuperAdminAsProxy(proxy);

        // 사전 겹침 검증
        Map<String, Object> overlapParams = new HashMap<>();
        overlapParams.put("company_code", companyCode);
        overlapParams.put("original_user_id", original);
        overlapParams.put("proxy_user_id", proxy);
        overlapParams.put("start_date", params.get("start_date"));
        overlapParams.put("end_date", endDate);
        if (checkOverlap(overlapParams) > 0) {
            throw new IllegalArgumentException("같은 대상-대무자 쌍의 활성 기간이 겹칩니다.");
        }
    }

    private void validateUserInCompany(String userId, String companyCode, String which) {
        Map<String, Object> p = new HashMap<>();
        p.put("user_id", userId);
        p.put("company_code", companyCode);
        Integer cnt = sqlSession.selectOne(NS + "countUserInCompany", p);
        if (cnt == null || cnt == 0) {
            throw new IllegalArgumentException(
                "original".equals(which)
                    ? "대상 사용자가 회사에 존재하지 않습니다."
                    : "대무자가 회사에 존재하지 않습니다.");
        }
    }

    private void rejectSuperAdminAsProxy(String userId) {
        Map<String, Object> p = new HashMap<>();
        p.put("user_id", userId);
        Integer cnt = sqlSession.selectOne(NS + "countSuperAdmin", p);
        if (cnt != null && cnt > 0) {
            throw new IllegalArgumentException("SUPER_ADMIN 은 대무자로 지정할 수 없습니다.");
        }
    }

    private void requireAdmin(Map<String, Object> params) {
        String role = (String) params.get("role");
        if (!"ADMIN".equals(role) && !"COMPANY_ADMIN".equals(role) && !"SUPER_ADMIN".equals(role)) {
            throw new AccessDeniedException("관리자만 대무자를 지정/수정/해지할 수 있습니다.");
        }
    }
}
