package com.bookshop.behavioral.chainofresponsibility;

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

import com.bookshop.domain.Book;
import com.bookshop.domain.Customer;
import java.math.BigDecimal;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Chain of Responsibility -- order checks as a configurable pipeline")
class OrderValidationChainTest {

    private final Book inStockBook = new Book("978-0132350884", "Clean Code", "Robert C. Martin",
            new BigDecimal("32.50"));
    private final Book rareBook = new Book("978-0000000001", "Out of Print Rarity", "Unknown",
            new BigDecimal("950.00"));
    private final Customer loyalAlice = new Customer("c-1", "Alice", "alice@example.com", 4);
    private final Customer newBob = new Customer("c-2", "Bob", "bob@example.com", 0);

    private final OrderCheck chain = OrderCheck.chainOf(List.of(
            new StockCheck(Set.of("978-0132350884")),
            new AddressCheck(),
            new FraudCheck()));

    @Test
    @DisplayName("a clean order passes every check in the chain")
    void validOrderPasses() {
        OrderRequest request = new OrderRequest(loyalAlice, List.of(inStockBook), "1 High Street");

        assertTrue(chain.validate(request).valid());
    }

    @Test
    @DisplayName("a failing check stops the chain and names itself -- later checks never run")
    void rejectionShortCircuits() {
        OrderRequest request = new OrderRequest(newBob, List.of(rareBook), "");

        ValidationResult result = chain.validate(request);

        assertFalse(result.valid());
        assertEquals("stock", result.rejectedBy());
    }

    @Test
    @DisplayName("the pipeline is configuration -- reordering the list reorders the checks")
    void chainOrderIsConfigurable() {
        OrderCheck addressFirst = OrderCheck.chainOf(List.of(new AddressCheck(), new FraudCheck()));
        OrderRequest request = new OrderRequest(newBob, List.of(rareBook), "");

        assertEquals("address", addressFirst.validate(request).rejectedBy());
    }

    @Test
    @DisplayName("relinking the same checks into a shorter chain drops the old tail")
    void relinkedShorterChainHasNoStaleTail() {
        StockCheck stock = new StockCheck(Set.of(rareBook.isbn()));
        OrderCheck.chainOf(List.of(stock, new FraudCheck()));
        OrderRequest fraudWouldReject = new OrderRequest(newBob, List.of(rareBook), "1 High Street");

        OrderCheck stockOnly = OrderCheck.chainOf(List.of(stock));

        assertTrue(stockOnly.validate(fraudWouldReject).valid());
    }

    @Test
    @DisplayName("each check is a small unit, testable entirely on its own")
    void checksAreIndependentlyTestable() {
        ValidationResult result = new FraudCheck()
                .validate(new OrderRequest(newBob, List.of(rareBook), "1 High Street"));

        assertEquals("fraud", result.rejectedBy());
        assertEquals("large first order needs manual review", result.reason());
    }
}
