"""Shared domain policies."""

from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal
from typing import Protocol

from .enums import CustomerType
from .exceptions import InsufficientStockError
from .entities import Customer, Product
from .value_objects import Money, Quantity


class DiscountPolicy(Protocol):
    def calculate_discount(self, customer: Customer, order_total: Money) -> Money: ...


@dataclass(frozen=True)
class TieredDiscountPolicy:
    vip_threshold: Decimal = Decimal("5000000")
    standard_threshold: Decimal = Decimal("2000000")
    vip_rate: Decimal = Decimal("0.10")
    standard_rate: Decimal = Decimal("0.05")

    def calculate_discount(self, customer: Customer, order_total: Money) -> Money:
        if not isinstance(customer, Customer):
            raise TypeError("customer must be a Customer instance")
        if customer.customer_type == CustomerType.VIP and order_total.amount >= self.vip_threshold:
            return order_total.percentage(self.vip_rate)
        if order_total.amount >= self.standard_threshold:
            return order_total.percentage(self.standard_rate)
        return Money.zero()


class StockPolicy(Protocol):
    def can_reserve(self, product: Product, quantity: Quantity) -> bool: ...
    def ensure_reservable(self, product: Product, quantity: Quantity) -> None: ...


@dataclass(frozen=True)
class DefaultStockPolicy:
    def can_reserve(self, product: Product, quantity: Quantity) -> bool:
        if not isinstance(product, Product):
            raise TypeError("product must be a Product instance")
        if not isinstance(quantity, Quantity):
            raise TypeError("quantity must be a Quantity instance")
        return product.can_reserve(quantity)

    def ensure_reservable(self, product: Product, quantity: Quantity) -> None:
        if not self.can_reserve(product, quantity):
            raise InsufficientStockError(
                f"not enough stock for {product.sku.code}: requested={quantity.amount}, available={product.stock_qty}"
            )
