package com.ga.api.service.settle;

import com.ga.api.service.deduction.DeductionService;
import com.ga.api.service.tax.TaxBreakdown;
import com.ga.api.service.tax.TaxCalculator;
import com.ga.api.service.transfer.TransferFileService;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.org.AgentMapper;
import com.ga.core.mapper.settle.PaymentDeductionDetailMapper;
import com.ga.core.mapper.settle.PaymentMapper;
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.PaymentDeductionDetailVO;
import com.ga.core.vo.settle.PaymentResp;
import com.ga.core.vo.settle.PaymentVO;
import com.ga.core.vo.settle.SettleSearchParam;
import com.ga.core.vo.settle.TransferFileResp;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
public class PaymentService {

    private final PaymentMapper mapper;
    private final AgentMapper agentMapper;
    private final PaymentDeductionDetailMapper deductionDetailMapper;
    private final TransferFileService transferFileService;
    private final TaxCalculator taxCalculator;
    private final DeductionService deductionService;

    @Transactional(readOnly = true)
    public PageResponse<PaymentResp> list(SettleSearchParam param) {
        param.startPage();
        return PageResponse.of(mapper.selectList(param));
    }

    @Transactional
    public void updateStatus(Long paymentId, String status, String failReason) {
        mapper.updateStatus(paymentId, status, failReason);
    }

    public TransferFileResp generateTransferFile(String settleMonth, String bankCode) {
        return transferFileService.generate(settleMonth, bankCode);
    }

    @Transactional
    public void calculateTaxes(Long paymentId) {
        PaymentVO payment = mapper.selectById(paymentId);
        if (payment == null) throw new BizException(ErrorCode.NOT_FOUND);
        applyTaxes(payment);
        mapper.updateTaxes(payment.getPaymentId(),
                payment.getIncomeTaxAmount(), payment.getLocalTaxAmount(),
                payment.getVatAmount(), payment.getNetAmount());
    }

    @Transactional
    public void calculateTaxesForMonth(String settleMonth) {
        List<PaymentVO> payments = mapper.selectBySettleMonth(settleMonth);
        for (PaymentVO payment : payments) {
            applyTaxes(payment);
        }
        if (!payments.isEmpty()) {
            mapper.updateTaxesBatch(payments);
        }
    }

    @Transactional
    public void applyDeductions(Long paymentId, String settleMonth) {
        PaymentVO payment = mapper.selectById(paymentId);
        if (payment == null) throw new BizException(ErrorCode.NOT_FOUND);

        List<AgentDeductionVO> pending = deductionService.selectPendingByAgent(payment.getAgentId(), settleMonth);
        if (pending.isEmpty()) return;

        // 명세 기록
        List<PaymentDeductionDetailVO> details = pending.stream().map(d -> {
            PaymentDeductionDetailVO detail = new PaymentDeductionDetailVO();
            detail.setPaymentId(paymentId);
            detail.setDeductionType(d.getDeductionType());
            detail.setAmount(d.getAmount());
            detail.setDescription(d.getDescription());
            detail.setAppliedAt(LocalDateTime.now());
            return detail;
        }).collect(Collectors.toList());
        deductionDetailMapper.insertBatch(details);

        // 상태 전이
        List<Long> ids = pending.stream().map(AgentDeductionVO::getDeductionId).collect(Collectors.toList());
        deductionService.markAsApplied(ids);

        // deduction_amount + net_amount UPDATE
        // net_amount = amount − incomeTax − localTax + vat − deductionAmount
        BigDecimal deductionTotal = pending.stream()
                .map(AgentDeductionVO::getAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal currentNet = payment.getNetAmount() != null ? payment.getNetAmount() : payment.getPayAmount();
        BigDecimal newNet = currentNet.subtract(deductionTotal);
        mapper.updateDeduction(paymentId, deductionTotal, newNet);
    }

    @Transactional
    public void applyDeductionsForMonth(String settleMonth) {
        List<PaymentVO> payments = mapper.selectBySettleMonth(settleMonth);
        for (PaymentVO payment : payments) {
            applyDeductions(payment.getPaymentId(), settleMonth);
        }
    }

    private void applyTaxes(PaymentVO payment) {
        AgentVO agent = agentMapper.selectById(payment.getAgentId());
        TaxType taxType = parseTaxType(agent);
        VatType vatType = parseVatType(agent);

        if (taxType == TaxType.BUSINESS_INCOME && (agent == null || agent.getBusinessNo() == null || agent.getBusinessNo().isBlank())) {
            log.warn("agentId={} taxType=BUSINESS_INCOME이지만 businessNo가 비어있습니다", payment.getAgentId());
        }

        TaxBreakdown breakdown = taxCalculator.calculate(payment.getPayAmount(), taxType, vatType);
        payment.setIncomeTaxAmount(breakdown.incomeTax());
        payment.setLocalTaxAmount(breakdown.localTax());
        payment.setVatAmount(breakdown.vat());
        payment.setNetAmount(breakdown.netAmount());
    }

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

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