package com.ruoyi.app.flashdelivery.service;

import com.ruoyi.system.domain.flash.FlashDeliveryPricing;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class FlashDeliveryPricingCalculatorTest {

    private final FlashDeliveryPricingCalculator calculator = new FlashDeliveryPricingCalculator();

    @Test
    void chargesOnlyStartingFareWithinStartingDistance() {
        var breakdown = calculator.calculateBreakdown(pricing(), 3000);

        assertEquals(new BigDecimal("0.00"), breakdown.getBillableDistance());
        assertEquals(0L, breakdown.getDistanceFee());
        assertEquals(90L, breakdown.getAmount());
    }

    @Test
    void appliesFoodDeliveryHalfKilometreBoundary() {
        assertBreakdown(3490, "0.00", 0L, 90L);
        assertBreakdown(3494, "0.00", 0L, 90L);
        assertBreakdown(3495, "0.00", 0L, 90L);
        assertBreakdown(3499, "0.00", 0L, 90L);
        assertBreakdown(3500, "1.00", 15L, 105L);
        assertBreakdown(3990, "1.00", 15L, 105L);
        assertBreakdown(4200, "1.20", 18L, 108L);
    }

    @Test
    void supportsConfiguredBillingDistanceAndRoundsTaiwanDollarHalfUp() {
        FlashDeliveryPricing pricing = pricing();
        pricing.setDistance(new BigDecimal("2.00"));
        pricing.setFreight(25L);

        var breakdown = calculator.calculateBreakdown(pricing, 6000);

        assertEquals(new BigDecimal("3.00"), breakdown.getBillableDistance());
        assertEquals(38L, breakdown.getDistanceFee());
        assertEquals(128L, breakdown.getAmount());

        pricing.setFreight(1L);
        assertEquals(1L, calculator.calculateBreakdown(pricing, 4000).getDistanceFee());

        pricing.setDistance(BigDecimal.ONE);
        pricing.setFreight(500L);
        assertEquals(601L, calculator.calculateBreakdown(pricing, 4201).getDistanceFee());
    }

    @Test
    void calculatesUrgentFeeFromBaseDeliveryFeeAndAddsTipSeparately() {
        var breakdown = calculator.calculateBreakdown(pricing(), 6000, "URGENT", 5L);

        assertEquals(45L, breakdown.getDistanceFee());
        assertEquals(135L, breakdown.getBaseDeliveryFee());
        assertEquals(27L, breakdown.getUrgentFee());
        assertEquals(5L, breakdown.getTipAmount());
        assertEquals(167L, breakdown.getAmount());
    }

    @Test
    void urgentFeeUsesMinimumAndRoundsPercentageHalfUp() {
        FlashDeliveryPricing pricing = pricing();
        pricing.setUrgentRate(new BigDecimal("12.50"));
        pricing.setMinimumUrgentFee(20L);
        assertEquals(20L, calculator.calculateBreakdown(pricing, 3000, "URGENT", 0L).getUrgentFee());

        pricing.setMinimumUrgentFee(0L);
        assertEquals(11L, calculator.calculateBreakdown(pricing, 3000, "URGENT", 0L).getUrgentFee());
    }

    @Test
    void normalDeliveryNeverChargesUrgentFee() {
        var breakdown = calculator.calculateBreakdown(pricing(), 3000, "NORMAL", 8L);
        assertEquals(0L, breakdown.getUrgentFee());
        assertEquals(98L, breakdown.getAmount());
    }

    @Test
    void rejectsInvalidDistanceOrPricing() {
        assertThrows(IllegalArgumentException.class, () -> calculator.calculateBreakdown(pricing(), 0));

        FlashDeliveryPricing invalidStartingDistance = pricing();
        invalidStartingDistance.setStartingDistance(BigDecimal.ZERO);
        assertThrows(IllegalArgumentException.class,
                () -> calculator.calculateBreakdown(invalidStartingDistance, 3000));

        FlashDeliveryPricing invalidStartingFare = pricing();
        invalidStartingFare.setStartingFare(0L);
        assertThrows(IllegalArgumentException.class,
                () -> calculator.calculateBreakdown(invalidStartingFare, 3000));

        FlashDeliveryPricing invalidDistance = pricing();
        invalidDistance.setDistance(BigDecimal.ZERO);
        assertThrows(IllegalArgumentException.class,
                () -> calculator.calculateBreakdown(invalidDistance, 3000));

        FlashDeliveryPricing invalidFreight = pricing();
        invalidFreight.setFreight(0L);
        assertThrows(IllegalArgumentException.class,
                () -> calculator.calculateBreakdown(invalidFreight, 3000));
    }

    private void assertBreakdown(int distanceMeters, String billableDistance, long distanceFee, long amount) {
        var breakdown = calculator.calculateBreakdown(pricing(), distanceMeters);
        assertEquals(new BigDecimal(billableDistance), breakdown.getBillableDistance());
        assertEquals(distanceFee, breakdown.getDistanceFee());
        assertEquals(amount, breakdown.getAmount());
    }

    private FlashDeliveryPricing pricing() {
        FlashDeliveryPricing pricing = new FlashDeliveryPricing();
        pricing.setStartingDistance(new BigDecimal("3.00"));
        pricing.setStartingFare(90L);
        pricing.setDistance(new BigDecimal("1.00"));
        pricing.setFreight(15L);
        pricing.setUrgentRate(new BigDecimal("20.00"));
        pricing.setMinimumUrgentFee(10L);
        return pricing;
    }
}
