from decimal import Decimal

import pytest

from .entities import Customer, Order, Product
from .exceptions import InsufficientStockError
from .decorators import DecoratorMetadata, constrains, depends, get_decorator_metadata, has_decorator, hook, onchange
from .services import InventoryService, PricingService
from .value_objects import Money, Quantity, SKU


def test_pricing_service_calculates_discount_and_final_total() -> None:
    customer = Customer(name="Chau")
    order = Order(customer=customer)
    product = Product(sku=SKU("SKU-100"), name="Monitor", price=Money(Decimal("1000000")), stock_qty=5)

    order.add_line(product, Quantity(2))

    service = PricingService()

    assert service.calculate_subtotal(order) == Money(Decimal("2000000"))
    assert service.calculate_discount(order) == Money(Decimal("100000"))
    assert service.calculate_final_total(order) == Money(Decimal("1900000"))


def test_inventory_service_reserve_order_updates_stock_and_blocks_oversell() -> None:
    keyboard = Product(sku=SKU("SKU-001"), name="Keyboard", price=Money(Decimal("500000")), stock_qty=10)
    mouse = Product(sku=SKU("SKU-002"), name="Mouse", price=Money(Decimal("250000")), stock_qty=1)
    order = Order(customer=Customer(name="Alice"))
    order.add_line(keyboard, Quantity(3))
    order.add_line(mouse, Quantity(2))

    service = InventoryService()

    with pytest.raises(InsufficientStockError):
        service.reserve_order(order)

    assert keyboard.stock_qty == 7
    assert mouse.stock_qty == 1


def test_domain_decorators_attach_metadata_without_changing_behavior() -> None:
    @depends("amount", "tax")
    @constrains("amount")
    @onchange("amount")
    @hook("save", name="after_save")
    def compute_total(amount: int, tax: int) -> int:
        return amount + tax

    assert compute_total(2, 3) == 5

    metadata = get_decorator_metadata(compute_total)
    assert metadata == (
        DecoratorMetadata(kind="hook", fields=("save",), name="after_save"),
        DecoratorMetadata(kind="onchange", fields=("amount",), name=None),
        DecoratorMetadata(kind="constrains", fields=("amount",), name=None),
        DecoratorMetadata(kind="depends", fields=("amount", "tax"), name=None),
    )
    assert has_decorator(compute_total, "depends") is True
    assert has_decorator(compute_total, "unknown") is False


def test_domain_decorators_validate_field_names() -> None:
    with pytest.raises(ValueError):
        depends()

    with pytest.raises(ValueError):
        constrains(" ")
