"""Purchases entities."""

from __future__ import annotations

from dataclasses import dataclass, field
from decimal import Decimal

from ..common.entities import Product
from ..common.value_objects import Money, Quantity
from .enums import PurchaseOrderState
from .exceptions import InvalidPurchaseOrderError, InvalidPurchaseOrderLineError, InvalidSupplierError, InvalidPurchaseTransitionError
from .policies import DefaultPurchaseApprovalPolicy, DefaultPurchaseOrderTransitionPolicy, PurchaseApprovalPolicy, PurchaseOrderTransitionPolicy


@dataclass
class Supplier:
    name: str
    reference: str | None = None
    email: str | None = None

    def __post_init__(self) -> None:
        if not isinstance(self.name, str) or not self.name.strip():
            raise InvalidSupplierError("supplier name cannot be empty")
        if self.reference is not None and (not isinstance(self.reference, str) or not self.reference.strip()):
            raise InvalidSupplierError("supplier reference must be a non-empty string when provided")
        if self.email is not None and (not isinstance(self.email, str) or not self.email.strip()):
            raise InvalidSupplierError("supplier email must be a non-empty string when provided")


@dataclass
class PurchaseOrderLine:
    product: Product
    quantity: Quantity
    unit_price: Money | None = None
    discount_rate: Decimal = Decimal("0")

    def __post_init__(self) -> None:
        if not isinstance(self.product, Product):
            raise InvalidPurchaseOrderLineError("product must be a Product instance")
        if not isinstance(self.quantity, Quantity):
            raise InvalidPurchaseOrderLineError("quantity must be a Quantity instance")
        if self.unit_price is None:
            self.unit_price = self.product.price
        if not isinstance(self.unit_price, Money):
            raise InvalidPurchaseOrderLineError("unit_price must be a Money instance")
        if isinstance(self.discount_rate, bool) or not isinstance(self.discount_rate, Decimal):
            raise InvalidPurchaseOrderLineError("discount_rate must be a Decimal")
        if self.discount_rate < 0 or self.discount_rate > 1:
            raise InvalidPurchaseOrderLineError("discount_rate must be between 0 and 1")

    @property
    def subtotal(self) -> Money:
        return self.unit_price.scaled_by(self.quantity.amount)

    @property
    def discount_amount(self) -> Money:
        return self.subtotal.percentage(self.discount_rate)

    @property
    def total(self) -> Money:
        return self.subtotal - self.discount_amount


@dataclass
class PurchaseOrder:
    supplier: Supplier
    state: PurchaseOrderState = PurchaseOrderState.DRAFT
    lines: list[PurchaseOrderLine] = field(default_factory=list)
    name: str | None = None
    tax_rate: Decimal = Decimal("0")

    def __post_init__(self) -> None:
        if not isinstance(self.supplier, Supplier):
            raise InvalidPurchaseOrderError("supplier must be a Supplier instance")
        if not isinstance(self.state, PurchaseOrderState):
            raise InvalidPurchaseOrderError("invalid purchase order state")
        if self.name is not None and (not isinstance(self.name, str) or not self.name.strip()):
            raise InvalidPurchaseOrderError("name must be a non-empty string when provided")
        if isinstance(self.tax_rate, bool) or not isinstance(self.tax_rate, Decimal):
            raise InvalidPurchaseOrderError("tax_rate must be a Decimal")
        if self.tax_rate < 0 or self.tax_rate > 1:
            raise InvalidPurchaseOrderError("tax_rate must be between 0 and 1")
        if any(not isinstance(line, PurchaseOrderLine) for line in self.lines):
            raise InvalidPurchaseOrderError("lines must contain PurchaseOrderLine instances")

    def add_line(
        self,
        product: Product,
        quantity: Quantity,
        *,
        unit_price: Money | None = None,
        discount_rate: Decimal = Decimal("0"),
    ) -> PurchaseOrderLine:
        self._ensure_editable()
        line = PurchaseOrderLine(product=product, quantity=quantity, unit_price=unit_price, discount_rate=discount_rate)
        self.lines.append(line)
        return line

    @property
    def amount_untaxed(self) -> Money:
        total = Money.zero()
        for line in self.lines:
            total = total + line.subtotal
        return total

    @property
    def amount_discount(self) -> Money:
        total = Money.zero()
        for line in self.lines:
            total = total + line.discount_amount
        return total

    @property
    def amount_tax(self) -> Money:
        taxable = self.amount_untaxed - self.amount_discount
        return taxable.percentage(self.tax_rate)

    @property
    def amount_total(self) -> Money:
        return self.amount_untaxed - self.amount_discount + self.amount_tax

    def send_rfq(self, *, policy: PurchaseOrderTransitionPolicy | None = None) -> None:
        self._transition(PurchaseOrderState.RFQ_SENT, policy=policy)

    def confirm(self, *, approval_policy: PurchaseApprovalPolicy | None = None, policy: PurchaseOrderTransitionPolicy | None = None) -> None:
        if approval_policy is None:
            approval_policy = DefaultPurchaseApprovalPolicy()
        target = PurchaseOrderState.TO_APPROVE if approval_policy.requires_approval(self.amount_total) else PurchaseOrderState.PURCHASE
        self._transition(target, policy=policy)

    def approve(self, *, policy: PurchaseOrderTransitionPolicy | None = None) -> None:
        self._transition(PurchaseOrderState.PURCHASE, policy=policy)

    def receive(self, *, policy: PurchaseOrderTransitionPolicy | None = None) -> None:
        self._transition(PurchaseOrderState.DONE, policy=policy)

    def cancel(self, *, policy: PurchaseOrderTransitionPolicy | None = None) -> None:
        self._transition(PurchaseOrderState.CANCELLED, policy=policy)

    def _transition(self, target_state: PurchaseOrderState, *, policy: PurchaseOrderTransitionPolicy | None = None) -> None:
        if policy is None:
            policy = DefaultPurchaseOrderTransitionPolicy()
        if not policy.can_transition(self.state, target_state):
            raise InvalidPurchaseTransitionError(f"cannot move purchase order from {self.state.value} to {target_state.value}")
        self.state = target_state

    def _ensure_editable(self) -> None:
        if self.state in {PurchaseOrderState.DONE, PurchaseOrderState.CANCELLED}:
            raise InvalidPurchaseOrderError("purchase order is closed")
