package inventory;

import inventory.DiscountCalculator.DiscountResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

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

/**
 * Unit tests for DiscountCalculator class.
 */
class DiscountCalculatorTest {

    private Product book;
    private Product electronics;

    /**
     * Set up test data before each test
     */
    @BeforeEach
    void setUp() {
        book = ProductFactory.createProduct("B001", "Test Book", "BOOK", 20.0, 10);
        electronics = ProductFactory.createProduct("E001", "Test Electronics", "ELECTRONICS", 100.0, 10);
    }

    /**
     * Test student discount on books
     */
    @Test
    void testStudentDiscountOnBooks() {
        DiscountResult discount = DiscountCalculator.calculateDiscount(book, 1, "STUDENT");
        assertNotNull(discount);
        assertEquals(1 * book.getPrice() * 0.10, discount.getDiscountAmount());
        assertEquals("Student discount: 10% off books", discount.getDescription());
    }

    /**
     * Test student discount on electronics (should not apply)
     */
    @Test
    void testStudentDiscountOnElectronics() {
        DiscountResult discount = DiscountCalculator.calculateDiscount(electronics, 1, "STUDENT");
        assertNotNull(discount);
        assertEquals(0, discount.getDiscountAmount());
        assertEquals("Student discount only applies to books", discount.getDescription());
    }

    /**
     * test bulk discount when quantity >= 5
     */
    @ParameterizedTest(name = "quantity = {arguments}")
    @ValueSource(ints = {5,10})
    void testBulkDiscountValid(int quantity) {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, quantity, "BULK");
        assertNotNull(discountBook);
        assertEquals(quantity * book.getPrice() * 0.15, discountBook.getDiscountAmount());
        assertEquals("Bulk discount: 15% off for 5+ items", discountBook.getDescription());

        DiscountResult discountElectronics = DiscountCalculator.calculateDiscount(electronics, quantity, "BULK");
        assertNotNull(discountElectronics);
        assertEquals(quantity * electronics.getPrice() * 0.15, discountElectronics.getDiscountAmount());
        assertEquals("Bulk discount: 15% off for 5+ items", discountElectronics.getDescription());
    }

    /**
     * Test bulk discount when quantity < 5
     */
    @Test
    void testBulkDiscountInvalid() {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 2, "BULK");
        assertNotNull(discountBook);
        assertEquals(0, discountBook.getDiscountAmount());
        assertEquals("Bulk discount requires 5+ items", discountBook.getDescription());
    }

    /**
     * Test no discount option
     */
    @Test
    void testNoDiscount() {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 1, "NONE");
        assertNotNull(discountBook);
        assertEquals(0, discountBook.getDiscountAmount());
        assertEquals("No discount applied", discountBook.getDescription());
    }

    /**
     * Test boundary conditions
     */
    @ParameterizedTest(name = "quantity =  {0}, discount(%) = {1}")
    @CsvSource({"1,0", "5,0.15", "100,0.15", "1000,0.15", "10000,0.15"})
    void testBoundaryConditions(int quantity, double discountPercentage) {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, quantity, "BULK");
        assertNotNull(discountBook);
        assertEquals(book.getPrice() * quantity * discountPercentage, discountBook.getDiscountAmount());
    }

    /**
     * Test discount calculation accuracy
     */
    @Test
    void testDiscountCalculationAccuracy() {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 2, "STUDENT");
        assertNotNull(discountBook);
        assertEquals(4, discountBook.getDiscountAmount());

        DiscountResult discountElectronics = DiscountCalculator.calculateDiscount(electronics, 5, "BULK");
        assertNotNull(discountElectronics);
        assertEquals(75, discountElectronics.getDiscountAmount());
    }

    @ParameterizedTest(name = "type = {0}")
    @ValueSource(strings = {"STUDENT", "student", "STUdent", "stuDent", "sTudenT"})
    void testCaseSensitivityOfDiscountTypes(String type) {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 1, type);
        assertNotNull(discountBook);
        assertEquals(2, discountBook.getDiscountAmount());
    }

    @ParameterizedTest(name = "type = {0}")
    @ValueSource(strings = {"", "--", "\n", "09999", "STUDENT1", "BULKSTUDENT"})
    void testInvalidDiscountTypes(String type) {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 1, type);
        assertNotNull(discountBook);
        assertEquals(0, discountBook.getDiscountAmount());
    }

    @Test
    void testDiscountZeroQuantity() {
        DiscountResult discountBook = DiscountCalculator.calculateDiscount(book, 0, "STUDENT");
        assertNotNull(discountBook);
        assertEquals(0, discountBook.getDiscountAmount());

        DiscountResult discountElectronics = DiscountCalculator.calculateDiscount(electronics, 0, "BULK");
        assertNotNull(discountElectronics);
        assertEquals(0, discountElectronics.getDiscountAmount());
    }
}