package com.bookshop.structural.facade;

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

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

@DisplayName("Facade -- one checkout() call orchestrates inventory, payment and shipping")
class CheckoutFacadeTest {

    private final Book book = new Book("978-0132350884", "Clean Code", "Robert C. Martin",
            new BigDecimal("32.50"));
    private final Customer alice = new Customer("c-1", "Alice", "alice@example.com", 1);

    /** Records every charge so tests can assert what the facade did -- or didn't -- ask for. */
    private static class RecordingPaymentService extends PaymentService {
        private int chargesAttempted;

        @Override
        public String charge(Customer customer, BigDecimal amount) {
            chargesAttempted++;
            return super.charge(customer, amount);
        }
    }

    private InventoryService inventory;
    private RecordingPaymentService payments;
    private CheckoutFacade checkout;

    @BeforeEach
    void setUp() {
        inventory = new InventoryService();
        payments = new RecordingPaymentService();
        checkout = new CheckoutFacade(inventory, payments, new ShippingService());
    }

    @Test
    @DisplayName("a single call performs the whole flow and returns everything the caller needs")
    void oneCallDoesEverything() {
        inventory.stock(book, 3);

        CheckoutSummary summary = checkout.checkout(alice, List.of(book), "1 High Street");

        assertNotNull(summary.transactionId());
        assertNotNull(summary.shipmentId());
        assertEquals(new BigDecimal("32.50"), summary.totalCharged());
        assertEquals(2, inventory.stockLevel(book));
    }

    @Test
    @DisplayName("the facade enforces the right order: no stock means no charge, ever")
    void outOfStockStopsTheFlowBeforePayment() {
        assertThrows(IllegalStateException.class,
                () -> checkout.checkout(alice, List.of(book), "1 High Street"));

        assertEquals(0, payments.chargesAttempted, "payment must never be attempted without stock");
        assertEquals(0, inventory.stockLevel(book));
    }
}
