package com.ga.api.service.installment;

import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.core.mapper.installment.InstallmentPlanMapper;
import com.ga.core.mapper.installment.InstallmentRatioMapper;
import com.ga.core.vo.installment.InstallmentPlanResp;
import com.ga.core.vo.installment.InstallmentPlanSearchParam;
import com.ga.core.vo.installment.InstallmentPlanVO;
import com.ga.core.vo.installment.InstallmentRatioResp;
import com.ga.core.vo.installment.InstallmentRatioSaveReq;
import com.ga.core.vo.installment.InstallmentRatioSearchParam;
import com.ga.core.vo.installment.InstallmentRatioVO;
import com.ga.core.vo.installment.InstallmentStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Service
@RequiredArgsConstructor
public class InstallmentService {

    private final InstallmentPlanMapper planMapper;
    private final InstallmentRatioMapper ratioMapper;
    private final InstallmentPlanGenerator generator;

    // ---- Plan 조회 ----

    @Transactional(readOnly = true)
    public PageResponse<InstallmentPlanResp> list(InstallmentPlanSearchParam param) {
        param.startPage();
        return PageResponse.of(planMapper.selectList(param));
    }

    @Transactional(readOnly = true)
    public InstallmentPlanResp detail(Long planId) {
        InstallmentPlanResp resp = planMapper.selectDetailById(planId);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        return resp;
    }

    @Transactional(readOnly = true)
    public List<InstallmentPlanVO> selectScheduledForMonth(String settleMonth) {
        return planMapper.selectScheduledByMonth(settleMonth);
    }

    // ---- Ratio 조회 ----

    @Transactional(readOnly = true)
    public PageResponse<InstallmentRatioResp> listRatios(InstallmentRatioSearchParam param) {
        param.startPage();
        return PageResponse.of(ratioMapper.selectList(param));
    }

    // ---- Plan 생성 ----

    @Transactional
    public void createPlan(Long contractId, BigDecimal totalCommission,
                           LocalDate contractDate, String productCategory) {
        List<InstallmentPlanVO> plans = generator.generate(contractId, totalCommission, contractDate, productCategory);
        if (!plans.isEmpty()) {
            planMapper.insertBatch(plans);
        }
    }

    // ---- Plan 상태 전이 ----

    @Transactional
    public void payPlan(Long planId, Long paymentId, BigDecimal paidAmount) {
        InstallmentPlanVO plan = requirePlan(planId);
        if (!InstallmentStatus.SCHEDULED.code().equals(plan.getStatus())
                && !InstallmentStatus.DEFERRED.code().equals(plan.getStatus())) {
            throw new BizException(ErrorCode.INVALID_PARAMETER, "지급 처리 가능한 상태가 아닙니다");
        }
        plan.setStatus(InstallmentStatus.PAID.code());
        plan.setPaidAmount(paidAmount);
        plan.setPaidAt(LocalDateTime.now());
        plan.setPaymentId(paymentId);
        planMapper.update(plan);
    }

    @Transactional
    public void deferPlan(Long planId) {
        InstallmentPlanVO plan = requirePlan(planId);
        if (!InstallmentStatus.SCHEDULED.code().equals(plan.getStatus())) {
            throw new BizException(ErrorCode.INVALID_PARAMETER, "SCHEDULED 상태만 유예 가능합니다");
        }
        planMapper.updateStatus(planId, InstallmentStatus.DEFERRED.code());
    }

    @Transactional
    public void cancelPlan(Long planId) {
        InstallmentPlanVO plan = requirePlan(planId);
        if (InstallmentStatus.PAID.code().equals(plan.getStatus())) {
            throw new BizException(ErrorCode.INVALID_PARAMETER, "지급 완료된 분급은 취소할 수 없습니다");
        }
        planMapper.updateStatus(planId, InstallmentStatus.CANCELLED.code());
    }

    @Transactional
    public void payScheduledForMonth(String settleMonth) {
        List<InstallmentPlanVO> scheduled = planMapper.selectScheduledByMonth(settleMonth);
        for (InstallmentPlanVO plan : scheduled) {
            plan.setStatus(InstallmentStatus.PAID.code());
            plan.setPaidAmount(plan.getExpectedAmount());
            plan.setPaidAt(LocalDateTime.now());
            planMapper.update(plan);
        }
    }

    // ---- Ratio 생성 ----

    @Transactional
    public void createRatio(InstallmentRatioSaveReq req) {
        InstallmentRatioVO vo = new InstallmentRatioVO();
        vo.setProductCategory(req.getProductCategory());
        vo.setPlanYear(req.getPlanYear());
        vo.setPlanMonth(req.getPlanMonth());   // null=연 단위, 1~12=회차(월) 단위
        vo.setRatioPercent(req.getRatioPercent());
        vo.setEffectiveFrom(req.getEffectiveFrom());
        vo.setEffectiveTo(req.getEffectiveTo());
        vo.setIsActive(true);
        ratioMapper.insert(vo);
    }

    private InstallmentPlanVO requirePlan(Long planId) {
        InstallmentPlanVO plan = planMapper.selectById(planId);
        if (plan == null) throw new BizException(ErrorCode.NOT_FOUND);
        return plan;
    }
}
