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

import java.time.LocalDate;
import java.util.List;
import org.junit.jupiter.api.Test;

// Pruebas públicas de P2 para la jerarquía de productos. No las modifiquen.
class P2ProductsPublicTest {

    private static final LocalDate NOV_1 = LocalDate.of(2026, 11, 1);

    private final Product milk = new Product("LEC-01", "Leche evaporada", 420, 24, "Lácteos");
    private final BulkProduct rice = new BulkProduct("ARR-01", "Arroz a granel", 420, 25_000, "Abarrotes");
    private final PerishableProduct yogurt =
            new PerishableProduct("YOG-01", "Yogur de fresa 1 l", 690, 6, "Lácteos", NOV_1);

    @Test
    void theOldConstructorUsesTheGeneralCategory() {
        Product p = new Product("PAN-01", "Pan francés", 30, 40);

        assertEquals("general", p.category());
    }

    @Test
    void theNewConstructorKeepsTheCategory() {
        assertEquals("Lácteos", milk.category());
        assertEquals("Abarrotes", rice.category());
    }

    @Test
    void rejectsABlankCategory() {
        assertThrows(IllegalArgumentException.class, () -> new Product("LEC-01", "Leche", 420, 24, " "));
        assertThrows(IllegalArgumentException.class, () -> new BulkProduct("ARR-01", "Arroz", 420, 1000, null));
    }

    @Test
    void aUnitProductCostsPriceTimesQuantity() {
        assertEquals(1_260, milk.priceFor(3));
        assertEquals(0, milk.priceFor(0));
        assertFalse(milk.soldByWeight());
    }

    @Test
    void priceForRejectsANegativeQuantity() {
        assertThrows(IllegalArgumentException.class, () -> milk.priceFor(-1));
        assertThrows(IllegalArgumentException.class, () -> rice.priceFor(-1));
    }

    @Test
    void aBulkProductCostsByTheGram() {
        // S/ 4.20 el kilo: 1.5 kg cuestan S/ 6.30 y 250 g, S/ 1.05.
        assertEquals(420, rice.priceCents());
        assertEquals(630, rice.priceFor(1_500));
        assertEquals(105, rice.priceFor(250));
        assertTrue(rice.soldByWeight());
    }

    @Test
    void aBulkPriceIsRoundedToTheNearestCentWithHalvesUp() {
        BulkProduct sugar = new BulkProduct("AZU-01", "Azúcar rubia", 100, 10_000, "Abarrotes");

        assertEquals(0, sugar.priceFor(4));      // 0.4 céntimos
        assertEquals(1, sugar.priceFor(5));      // 0.5 céntimos
        assertEquals(1, sugar.priceFor(14));     // 1.4 céntimos
    }

    @Test
    void stockValueUsesThePriceOfTheWholeStock() {
        assertEquals(10_080, milk.stockValueCents());
        assertEquals(10_500, rice.stockValueCents());   // 25 kg x S/ 4.20
    }

    @Test
    void aBulkStockIsInGrams() {
        rice.removeStock(1_500);
        rice.addStock(500);

        assertEquals(24_000, rice.stock());
        assertThrows(IllegalStateException.class, () -> rice.removeStock(24_001));
    }

    @Test
    void lowStockThresholdIsInUnitsOrInKilograms() {
        BulkProduct beans = new BulkProduct("FRE-01", "Frejol canario", 1_150, 5_000, "Abarrotes");

        assertTrue(milk.isLowStock(24));
        assertFalse(milk.isLowStock(23));
        assertTrue(beans.isLowStock(5));
        assertFalse(beans.isLowStock(4));
        assertThrows(IllegalArgumentException.class, () -> milk.isLowStock(-1));
    }

    @Test
    void aPerishableProductExpiresTheDayAfterItsDate() {
        assertEquals(NOV_1, yogurt.expiresOn());
        assertFalse(yogurt.isExpiredOn(NOV_1));
        assertTrue(yogurt.isExpiredOn(NOV_1.plusDays(1)));
        assertFalse(yogurt.soldByWeight());
        assertEquals(2_070, yogurt.priceFor(3));
    }

    @Test
    void aPerishableProductNeedsADate() {
        assertThrows(NullPointerException.class,
                () -> new PerishableProduct("YOG-02", "Yogur natural", 650, 3, "Lácteos", null));
    }

    @Test
    void equalityIsByCodeWhateverTheKindOfProduct() {
        Product plain = new Product("ARR-01", "Arroz", 420, 1);

        assertEquals(plain, rice);
        assertEquals(rice, plain);
        assertEquals(plain.hashCode(), rice.hashCode());
    }

    @Test
    void theInventoryWorksWithEveryKindOfProduct() {
        Inventory inv = new Inventory();
        inv.add(milk);
        inv.add(rice);
        inv.add(yogurt);

        assertEquals(10_080 + 10_500 + 4_140, inv.totalValueCents());
        assertEquals(List.of(yogurt), inv.lowStock(6));
        assertEquals(List.of(milk, yogurt), inv.lowStock(24));   // el arroz tiene 25 kg
        assertEquals(List.of(milk, rice, yogurt), inv.lowStock(25));
    }

    @Test
    void aListOfProductsCallsTheRightPriceFor() {
        List<Product> products = List.of(milk, rice, yogurt);
        long total = 0;
        for (Product p : products) {
            total += p.priceFor(1_000);
        }

        assertEquals(420_000 + 420 + 690_000, total);
    }
}
