package com.ga.batch.settlement.calc;

import com.ga.core.mapper.org.AgentMapper;
import com.ga.core.mapper.rule.RuleMapper;
import com.ga.core.vo.ledger.LedgerVO;
import com.ga.core.vo.org.AgentVO;
import com.ga.core.vo.product.ContractResp;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.math.BigDecimal;
import java.time.LocalDate;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CommissionCalculatorTest {

    @Mock RuleMapper ruleMapper;
    @Mock AgentMapper agentMapper;

    @InjectMocks CommissionCalculator calculator;

    private ContractResp contract;

    @BeforeEach
    void setUp() {
        contract = new ContractResp();
        contract.setContractId(100L);
        contract.setAgentId(10L);
        contract.setProductId(1L);
        contract.setPolicyNo("POL-0001");
        contract.setCompanyCode("SAMSUNG");
        contract.setProductCode("PRD001");
        contract.setInsuranceType("WHOLE_LIFE");
        contract.setPremium(new BigDecimal("1000000"));
        contract.setPayCycle("MONTHLY");
        contract.setContractDate(LocalDate.of(2024, 1, 15));

        AgentVO agent = new AgentVO();
        agent.setAgentId(10L);
        agent.setGradeId(5L);
        when(agentMapper.selectById(10L)).thenReturn(agent);
    }

    @Test
    @DisplayName("calcRecruitLedger: premium × company_rate × payout_rate, 세금은 3.3%")
    void recruit_basic() {
        when(ruleMapper.findCompanyRate(eq(1L), eq(1), any(LocalDate.class)))
                .thenReturn(new BigDecimal("7.5"));
        when(ruleMapper.findPayoutRate(eq(5L), eq("WHOLE_LIFE"), eq(1), any(LocalDate.class)))
                .thenReturn(new BigDecimal("60.0"));

        LedgerVO led = calculator.calcRecruitLedger(contract, "202405");

        assertThat(led).isNotNull();
        assertThat(led.getCommissionYear()).isEqualTo(1);
        assertThat(led.getSettleMonth()).isEqualTo("202405");
        assertThat(led.getStatus()).isEqualTo("CALCULATED");
        assertThat(led.getPremium()).isEqualByComparingTo("1000000");
        assertThat(led.getCompanyRate()).isEqualByComparingTo("7.5");
        assertThat(led.getPayoutRate()).isEqualByComparingTo("60.0");
        // 1,000,000 × 7.5% = 75,000.00
        assertThat(led.getCompanyAmount()).isEqualByComparingTo("75000.00");
        // 75,000 × 60% = 45,000.00
        assertThat(led.getAgentAmount()).isEqualByComparingTo("45000.00");
        // 45,000 × 3.3% = 1,485.00
        assertThat(led.getTaxAmount()).isEqualByComparingTo("1485.00");
        assertThat(led.getContractId()).isEqualTo(100L);
        assertThat(led.getAgentId()).isEqualTo(10L);
        assertThat(led.getPolicyNo()).isEqualTo("POL-0001");
        assertThat(led.getCompanyCode()).isEqualTo("SAMSUNG");
        assertThat(led.getProductCode()).isEqualTo("PRD001");
        assertThat(led.getInsuranceType()).isEqualTo("WHOLE_LIFE");
        assertThat(led.getPayMethod()).isEqualTo("MONTHLY");
    }

    @Test
    @DisplayName("calcMaintainLedger: 회차에 따라 commission_year 가 다름")
    void maintain_year3() {
        when(ruleMapper.findCompanyRate(eq(1L), eq(3), any(LocalDate.class)))
                .thenReturn(new BigDecimal("3.0"));
        when(ruleMapper.findPayoutRate(eq(5L), eq("WHOLE_LIFE"), eq(3), any(LocalDate.class)))
                .thenReturn(new BigDecimal("50.0"));

        LedgerVO led = calculator.calcMaintainLedger(contract, "202405", 3);

        assertThat(led.getCommissionYear()).isEqualTo(3);
        // 1,000,000 × 3% = 30,000
        assertThat(led.getCompanyAmount()).isEqualByComparingTo("30000.00");
        // 30,000 × 50% = 15,000
        assertThat(led.getAgentAmount()).isEqualByComparingTo("15000.00");
        // 15,000 × 3.3% = 495
        assertThat(led.getTaxAmount()).isEqualByComparingTo("495.00");
    }

    @Test
    @DisplayName("agent 가 존재하지 않으면 null 반환")
    void agentNotFound() {
        when(agentMapper.selectById(10L)).thenReturn(null);

        LedgerVO led = calculator.calcRecruitLedger(contract, "202405");

        assertThat(led).isNull();
    }

    @Test
    @DisplayName("규정 누락 시 0 으로 폴백 → 모든 금액 0")
    void missingRates_fallbackZero() {
        when(ruleMapper.findCompanyRate(any(), any(), any())).thenReturn(null);
        when(ruleMapper.findPayoutRate(any(), any(), any(), any())).thenReturn(null);

        LedgerVO led = calculator.calcRecruitLedger(contract, "202405");

        assertThat(led).isNotNull();
        assertThat(led.getCompanyRate()).isEqualByComparingTo("0");
        assertThat(led.getPayoutRate()).isEqualByComparingTo("0");
        assertThat(led.getCompanyAmount()).isEqualByComparingTo("0.00");
        assertThat(led.getAgentAmount()).isEqualByComparingTo("0.00");
        assertThat(led.getTaxAmount()).isEqualByComparingTo("0.00");
    }

    @Test
    @DisplayName("contractDate 가 null 이면 오늘 날짜로 대체 (예외 없음)")
    void nullContractDate_usesToday() {
        contract.setContractDate(null);
        when(ruleMapper.findCompanyRate(eq(1L), eq(1), any(LocalDate.class)))
                .thenReturn(new BigDecimal("5"));
        when(ruleMapper.findPayoutRate(eq(5L), eq("WHOLE_LIFE"), eq(1), any(LocalDate.class)))
                .thenReturn(new BigDecimal("100"));

        LedgerVO led = calculator.calcRecruitLedger(contract, "202405");

        assertThat(led).isNotNull();
        // 1,000,000 × 5% × 100% = 50,000
        assertThat(led.getAgentAmount()).isEqualByComparingTo("50000.00");
    }

    @Test
    @DisplayName("premium 이 null 이면 0 으로 처리 (HALF_UP 0.00)")
    void nullPremium() {
        contract.setPremium(null);
        when(ruleMapper.findCompanyRate(any(), any(), any())).thenReturn(new BigDecimal("7.5"));
        when(ruleMapper.findPayoutRate(any(), any(), any(), any())).thenReturn(new BigDecimal("60"));

        LedgerVO led = calculator.calcRecruitLedger(contract, "202405");

        assertThat(led.getPremium()).isEqualByComparingTo("0");
        assertThat(led.getCompanyAmount()).isEqualByComparingTo("0.00");
        assertThat(led.getAgentAmount()).isEqualByComparingTo("0.00");
    }
}
