package library;

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

import java.time.LocalDate;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class LibraryTest {
    private static final LocalDate SEPT_1 = LocalDate.of(2026, 9, 1);

    private final Book book = new Book("0001", "Cuentos del Rímac", "Rosa Quispe");
    private Library lib;
    private Copy copy1;
    private Copy copy2;
    private Member alice;

    @BeforeEach
    void setUp() {
        lib = new Library();
        copy1 = new Copy("B-001", book);
        copy2 = new Copy("B-002", book);
        lib.addCopy(copy1);
        lib.addCopy(copy2);
        alice = new Member("S-17", "Alice");
        lib.addMember(alice);
    }

    @Test
    void findCopyByCode() {
        assertSame(copy2, lib.findCopy("B-002"));
        assertNull(lib.findCopy("B-999"));
    }

    @Test
    void lendMarksCopyAndMember() {
        Loan loan = lib.lend(copy1, alice, SEPT_1);

        assertFalse(copy1.isAvailable());
        assertEquals(1, alice.activeLoanCount());
        assertEquals(1, lib.availableCopies(book));
        assertEquals(LocalDate.of(2026, 9, 15), loan.due());
    }

    @Test
    void cannotLendACopyThatIsOnLoan() {
        lib.lend(copy1, alice, SEPT_1);
        Member bob = new Member("S-18", "Bob");

        assertThrows(IllegalArgumentException.class, () -> lib.lend(copy1, bob, SEPT_1));
    }

    @Test
    void memberCannotExceedMaxLoans() {
        for (int i = 0; i < Member.MAX_LOANS; i++) {
            Copy c = new Copy("X-" + i, book);
            lib.addCopy(c);
            lib.lend(c, alice, SEPT_1);
        }

        assertFalse(alice.canBorrow());
        assertThrows(IllegalArgumentException.class, () -> lib.lend(copy1, alice, SEPT_1));
    }

    @Test
    void overdueOnlyAfterDueDate() {
        Loan loan = lib.lend(copy1, alice, SEPT_1);

        assertFalse(loan.isOverdue(LocalDate.of(2026, 9, 15)));
        assertTrue(loan.isOverdue(LocalDate.of(2026, 9, 16)));
        assertEquals(1, alice.overdueCount(LocalDate.of(2026, 9, 16)));
    }

    @Test
    void giveBackFreesCopyAndClearsOverdue() {
        Loan loan = lib.lend(copy1, alice, SEPT_1);

        lib.giveBack(loan);

        assertTrue(copy1.isAvailable());
        assertEquals(0, alice.activeLoanCount());
        assertFalse(loan.isOverdue(LocalDate.of(2026, 12, 31)));
    }

    @Test
    void cannotGiveBackTwice() {
        Loan loan = lib.lend(copy1, alice, SEPT_1);
        lib.giveBack(loan);

        assertThrows(IllegalArgumentException.class, () -> lib.giveBack(loan));
    }
}
