package com.ga.batch.settlement.step;

import com.ga.batch.settlement.SettlementContext;
import com.ga.batch.settlement.domain.BatchTaxCalculator;
import com.ga.batch.settlement.domain.TaxBreakdown;
import com.ga.common.util.MoneyUtil;
import com.ga.core.mapper.ledger.ExceptionLedgerMapper;
import com.ga.core.mapper.ledger.MaintainLedgerMapper;
import com.ga.core.mapper.ledger.RecruitLedgerMapper;
import com.ga.core.mapper.org.AgentMapper;
import com.ga.core.mapper.settle.AgentDeductionMapper;
import com.ga.core.mapper.settle.ChargebackMapper;
import com.ga.core.mapper.settle.CommissionStatementMapper;
import com.ga.core.mapper.settle.OverrideSettleMapper;
import com.ga.core.mapper.settle.SettleMasterMapper;
import com.ga.core.vo.org.AgentResp;
import com.ga.core.vo.org.AgentSearchParam;
import com.ga.core.vo.org.AgentVO;
import com.ga.core.vo.org.TaxType;
import com.ga.core.vo.org.VatType;
import com.ga.core.vo.settle.AgentDeductionVO;
import com.ga.core.vo.settle.CommissionStatementVO;
import com.ga.core.vo.settle.DeductionStatus;
import com.ga.core.vo.settle.SettleMasterVO;
import com.ga.core.vo.settle.StatementType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.StepContribution;
import org.springframework.batch.core.configuration.annotation.JobScope;
import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Step 8 — aggregate: 설계사별 통합 집계 → settle_master UPSERT
 *
 * 통합 내용:
 * - BatchTaxCalculator: 설계사 세금유형(TaxType/VatType)에 따라 정확한 세금 계산
 *   (사업소득 3.3% / 기타소득 8.8% + 지방소득세 10% + 부가세 10%)
 * - AgentDeductionMapper: PENDING 차감 합산 → netAmount에서 차감 후 APPLIED 상태로 전이
 * - CommissionStatementMapper: 설계사별 명세서 레코드(ISSUED 상태) INSERT
 */
@Slf4j
@Component
@JobScope
@RequiredArgsConstructor
public class AggregateStep implements Tasklet {

    private final SettlementContext ctx;
    private final AgentMapper agentMapper;
    private final RecruitLedgerMapper recruitMapper;
    private final MaintainLedgerMapper maintainMapper;
    private final ExceptionLedgerMapper exceptionMapper;
    private final OverrideSettleMapper overrideMapper;
    private final ChargebackMapper chargebackMapper;
    private final SettleMasterMapper settleMapper;
    private final AgentDeductionMapper deductionMapper;
    private final CommissionStatementMapper statementMapper;
    private final BatchTaxCalculator taxCalculator;

    @Override
    public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) {
        log.info("[Step8 aggregate] settleMonth={}", ctx.getSettleMonth());

        Map<Long, BigDecimal> recruitMap    = toMap(recruitMapper.aggregateByAgent(ctx.getSettleMonth()));
        Map<Long, BigDecimal> maintainMap   = toMap(maintainMapper.aggregateByAgent(ctx.getSettleMonth()));
        Map<Long, BigDecimal> chargebackMap = toMap(chargebackMapper.aggregateByAgent(ctx.getSettleMonth()));
        Map<Long, BigDecimal> overrideMap   = toMap(overrideMapper.aggregateByUpper(ctx.getSettleMonth()));

        // 예외는 PLUS/MINUS 분리
        Map<Long, BigDecimal> excPlus  = new HashMap<>();
        Map<Long, BigDecimal> excMinus = new HashMap<>();
        for (Map<String, Object> row : exceptionMapper.aggregateByAgent(ctx.getSettleMonth())) {
            Long agentId = ((Number) row.get("agentId")).longValue();
            excPlus.put(agentId, toBd(row.get("plus")));
            excMinus.put(agentId, toBd(row.get("minus")));
        }

        // 합집합 agent_id
        Set<Long> agentIds = new HashSet<>();
        agentIds.addAll(recruitMap.keySet());
        agentIds.addAll(maintainMap.keySet());
        agentIds.addAll(chargebackMap.keySet());
        agentIds.addAll(overrideMap.keySet());
        agentIds.addAll(excPlus.keySet());
        agentIds.addAll(excMinus.keySet());

        // 활성 설계사 누락 보충
        AgentSearchParam ap = new AgentSearchParam();
        ap.setStatus("ACTIVE");
        ap.setPageSize(Integer.MAX_VALUE);
        for (AgentResp a : agentMapper.selectList(ap)) {
            agentIds.add(a.getAgentId());
        }

        // 정산월 PENDING 차감 목록 일괄 조회 (배치 효율)
        List<AgentDeductionVO> allDeductions = deductionMapper.selectPendingByMonth(ctx.getSettleMonth());
        Map<Long, List<AgentDeductionVO>> deductionByAgent = new HashMap<>();
        for (AgentDeductionVO d : allDeductions) {
            deductionByAgent.computeIfAbsent(d.getAgentId(), k -> new ArrayList<>()).add(d);
        }

        List<Long> appliedDeductionIds = new ArrayList<>();
        List<CommissionStatementVO> statements = new ArrayList<>();
        int count = 0;

        for (Long agentId : agentIds) {
            BigDecimal recruit  = recruitMap.getOrDefault(agentId, BigDecimal.ZERO);
            BigDecimal maintain = maintainMap.getOrDefault(agentId, BigDecimal.ZERO);
            BigDecimal cb       = chargebackMap.getOrDefault(agentId, BigDecimal.ZERO);
            BigDecimal ov       = overrideMap.getOrDefault(agentId, BigDecimal.ZERO);
            BigDecimal ep       = excPlus.getOrDefault(agentId, BigDecimal.ZERO);
            BigDecimal em       = excMinus.getOrDefault(agentId, BigDecimal.ZERO);

            BigDecimal gross = MoneyUtil.add(MoneyUtil.add(MoneyUtil.add(recruit, maintain), ov), ep)
                                        .subtract(cb).subtract(em);

            // 세금유형 조회 (없으면 기본: BUSINESS_INCOME / NONE)
            AgentVO agentVO = agentMapper.selectById(agentId);
            TaxType taxType = parseTaxType(agentVO);
            VatType vatType = parseVatType(agentVO);

            // BatchTaxCalculator로 정확한 세금 계산
            TaxBreakdown tax = taxCalculator.calculate(gross, taxType, vatType);
            BigDecimal totalTax = tax.incomeTax().add(tax.localTax()); // settle_master.tax_amount

            // 차감 합산
            List<AgentDeductionVO> agentDeductions = deductionByAgent.getOrDefault(agentId, List.of());
            BigDecimal deductionTotal = agentDeductions.stream()
                    .map(AgentDeductionVO::getAmount)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal netAmount = tax.netAmount().subtract(deductionTotal);

            // settle_master UPSERT
            SettleMasterVO vo = new SettleMasterVO();
            vo.setAgentId(agentId);
            vo.setSettleMonth(ctx.getSettleMonth());
            vo.setRecruitTotal(recruit);
            vo.setMaintainTotal(maintain);
            vo.setExceptionPlus(ep);
            vo.setExceptionMinus(em);
            vo.setOverrideTotal(ov);
            vo.setChargebackTotal(cb);
            vo.setGrossAmount(gross);
            vo.setTaxAmount(totalTax);   // incomeTax + localTax 합산
            vo.setNetAmount(netAmount);  // gross - incomeTax - localTax + vat - deduction
            vo.setStatus("CALCULATED");
            settleMapper.upsert(vo);
            count++;

            // 차감 적용 완료 처리
            if (!agentDeductions.isEmpty()) {
                agentDeductions.forEach(d -> appliedDeductionIds.add(d.getDeductionId()));
            }

            // 명세서 레코드 생성 (commission_statement INSERT, 중복 시 skip)
            CommissionStatementVO existingStmt = statementMapper.selectByAgentMonth(
                    agentId, ctx.getSettleMonth(), StatementType.MONTHLY.code());
            if (existingStmt == null && gross.signum() != 0) {
                CommissionStatementVO stmt = new CommissionStatementVO();
                stmt.setAgentId(agentId);
                stmt.setSettleMonth(ctx.getSettleMonth());
                stmt.setStatementType(StatementType.MONTHLY.code());
                stmt.setGrossAmount(gross);
                stmt.setIncomeTaxAmount(tax.incomeTax());
                stmt.setLocalTaxAmount(tax.localTax());
                stmt.setVatAmount(tax.vat());
                stmt.setDeductionAmount(deductionTotal);
                stmt.setNetAmount(netAmount);
                stmt.setIssueStatus("ISSUED");
                stmt.setIssuedAt(LocalDateTime.now());
                statements.add(stmt);
            }
        }

        // 차감 APPLIED 상태 일괄 전이
        if (!appliedDeductionIds.isEmpty()) {
            deductionMapper.updateStatusBatch(appliedDeductionIds, DeductionStatus.APPLIED.name());
            log.info("[Step8] deductions applied: {}", appliedDeductionIds.size());
        }

        // 명세서 일괄 INSERT
        for (CommissionStatementVO stmt : statements) {
            statementMapper.insert(stmt);
        }
        if (!statements.isEmpty()) {
            log.info("[Step8] commission statements inserted: {}", statements.size());
        }

        ctx.setAggregateCount(count);
        log.info("[Step8] settle master upserted: {}", count);
        return RepeatStatus.FINISHED;
    }

    private TaxType parseTaxType(AgentVO agent) {
        if (agent == null || agent.getTaxType() == null) return TaxType.BUSINESS_INCOME;
        try {
            return TaxType.valueOf(agent.getTaxType());
        } catch (IllegalArgumentException e) {
            return TaxType.BUSINESS_INCOME;
        }
    }

    private VatType parseVatType(AgentVO agent) {
        if (agent == null || agent.getVatType() == null) return VatType.NONE;
        try {
            return VatType.valueOf(agent.getVatType());
        } catch (IllegalArgumentException e) {
            return VatType.NONE;
        }
    }

    private Map<Long, BigDecimal> toMap(List<Map<String, Object>> rows) {
        Map<Long, BigDecimal> m = new HashMap<>();
        for (Map<String, Object> r : rows) {
            Long id = ((Number) r.get("agentId")).longValue();
            m.put(id, toBd(r.get("total")));
        }
        return m;
    }

    private BigDecimal toBd(Object v) {
        if (v == null) return BigDecimal.ZERO;
        if (v instanceof BigDecimal bd) return bd;
        return new BigDecimal(v.toString());
    }
}
