"""Request schemas for the B2B ordering portal.

Responses are returned as plain dicts (FastAPI encodes them); these models
constrain and validate *inputs* only.
"""
from __future__ import annotations

from datetime import datetime

from pydantic import BaseModel, Field, field_validator

from app.models.ordering import (
    FULFILMENT_METHODS,
    ORDER_STATUSES,
    PRODUCT_CATEGORIES,
)

_CATEGORY_SET = set(PRODUCT_CATEGORIES)
_STATUS_SET = set(ORDER_STATUSES)
_FULFILMENT_SET = set(FULFILMENT_METHODS)
_RULE_TYPES = {"fixed", "contract", "quote"}
_STOCK_STATUSES = {"in_stock", "low_stock", "out_of_stock", "made_to_order"}


# --- Customer ordering -------------------------------------------------------


class OrderLineInput(BaseModel):
    product_id: int
    quantity: float = Field(gt=0)
    notes: str | None = None


class DraftOrderCreate(BaseModel):
    lines: list[OrderLineInput] = Field(default_factory=list)
    purchase_order_number: str | None = None
    delivery_notes: str | None = None
    requested_delivery_date: datetime | None = None
    fulfilment_method: str = "delivery"

    @field_validator("fulfilment_method")
    @classmethod
    def _check_fulfilment(cls, value: str) -> str:
        if value not in _FULFILMENT_SET:
            raise ValueError(f"fulfilment_method must be one of {sorted(_FULFILMENT_SET)}")
        return value


class DraftOrderUpdate(BaseModel):
    lines: list[OrderLineInput] | None = None
    purchase_order_number: str | None = None
    delivery_notes: str | None = None
    requested_delivery_date: datetime | None = None
    fulfilment_method: str | None = None

    @field_validator("fulfilment_method")
    @classmethod
    def _check_fulfilment(cls, value: str | None) -> str | None:
        if value is not None and value not in _FULFILMENT_SET:
            raise ValueError(f"fulfilment_method must be one of {sorted(_FULFILMENT_SET)}")
        return value


class OrderSubmitRequest(BaseModel):
    purchase_order_number: str | None = None
    delivery_notes: str | None = None
    requested_delivery_date: datetime | None = None
    fulfilment_method: str | None = None

    @field_validator("fulfilment_method")
    @classmethod
    def _check_fulfilment(cls, value: str | None) -> str | None:
        if value is not None and value not in _FULFILMENT_SET:
            raise ValueError(f"fulfilment_method must be one of {sorted(_FULFILMENT_SET)}")
        return value


# --- Admin: catalogue --------------------------------------------------------


class CategoryCreate(BaseModel):
    slug: str = Field(min_length=1, max_length=64)
    name: str = Field(min_length=1, max_length=255)
    description: str | None = None
    sort_order: int = 0
    active: bool = True


class CategoryUpdate(BaseModel):
    name: str | None = None
    description: str | None = None
    sort_order: int | None = None
    active: bool | None = None


class CatalogueProductCreate(BaseModel):
    name: str = Field(min_length=1, max_length=255)
    sku: str = Field(min_length=1, max_length=64)
    description: str | None = None
    category: str = "grains"
    image_url: str | None = None
    unit_size: str | None = None
    unit_of_measure: str = "each"
    min_order_quantity: float = Field(default=1.0, ge=0)
    base_price: float | None = Field(default=None, ge=0)
    stock_status: str = "in_stock"
    active: bool = True
    requires_quote: bool = False

    @field_validator("category")
    @classmethod
    def _check_category(cls, value: str) -> str:
        if value not in _CATEGORY_SET:
            raise ValueError(f"category must be one of {sorted(_CATEGORY_SET)}")
        return value

    @field_validator("stock_status")
    @classmethod
    def _check_stock(cls, value: str) -> str:
        if value not in _STOCK_STATUSES:
            raise ValueError(f"stock_status must be one of {sorted(_STOCK_STATUSES)}")
        return value


class CatalogueProductUpdate(BaseModel):
    name: str | None = None
    sku: str | None = None
    description: str | None = None
    category: str | None = None
    image_url: str | None = None
    unit_size: str | None = None
    unit_of_measure: str | None = None
    min_order_quantity: float | None = Field(default=None, ge=0)
    base_price: float | None = Field(default=None, ge=0)
    stock_status: str | None = None
    active: bool | None = None
    requires_quote: bool | None = None

    @field_validator("category")
    @classmethod
    def _check_category(cls, value: str | None) -> str | None:
        if value is not None and value not in _CATEGORY_SET:
            raise ValueError(f"category must be one of {sorted(_CATEGORY_SET)}")
        return value

    @field_validator("stock_status")
    @classmethod
    def _check_stock(cls, value: str | None) -> str | None:
        if value is not None and value not in _STOCK_STATUSES:
            raise ValueError(f"stock_status must be one of {sorted(_STOCK_STATUSES)}")
        return value


class VisibilityUpdate(BaseModel):
    product_id: int
    visible: bool


# --- Admin: pricing ----------------------------------------------------------


class PriceTierInput(BaseModel):
    min_quantity: float = Field(gt=0)
    unit_price: float = Field(ge=0)


class PriceListCreate(BaseModel):
    code: str = Field(min_length=1, max_length=64)
    name: str = Field(min_length=1, max_length=255)
    description: str | None = None
    active: bool = True


class PriceListItemUpsert(BaseModel):
    product_id: int
    unit_price: float = Field(ge=0)
    tiers: list[PriceTierInput] | None = None


class CustomerPriceAssignmentUpsert(BaseModel):
    price_list_id: int | None = None
    discount_percent: float = Field(default=0.0, ge=0, le=100)


class CustomerProductPriceUpsert(BaseModel):
    product_id: int
    unit_price: float | None = Field(default=None, ge=0)
    rule_type: str = "fixed"
    contract_reference: str | None = None
    notes: str | None = None
    active: bool = True
    tiers: list[PriceTierInput] | None = None

    @field_validator("rule_type")
    @classmethod
    def _check_rule(cls, value: str) -> str:
        if value not in _RULE_TYPES:
            raise ValueError(f"rule_type must be one of {sorted(_RULE_TYPES)}")
        return value


# --- Admin: orders -----------------------------------------------------------


class OrderStatusUpdate(BaseModel):
    to_status: str
    note: str | None = None

    @field_validator("to_status")
    @classmethod
    def _check_status(cls, value: str) -> str:
        if value not in _STATUS_SET:
            raise ValueError(f"to_status must be one of {sorted(_STATUS_SET)}")
        return value


class OrderLineOverride(BaseModel):
    quantity: float | None = Field(default=None, gt=0)
    unit_price: float | None = Field(default=None, ge=0)
    reason: str | None = None


class OrderAdminNotesUpdate(BaseModel):
    admin_notes: str | None = None


class ReopenOrderRequest(BaseModel):
    note: str | None = None


# --- Admin: settings ---------------------------------------------------------


class NotificationSettingsUpdate(BaseModel):
    internal_recipients: str | None = None
    send_customer_confirmation: bool | None = None
    require_po_number: bool | None = None
    from_email: str | None = None


# --- Admin: Xero integration -------------------------------------------------


class XeroContactLinkUpsert(BaseModel):
    """Link a customer to a Xero contact. ``xero_contact_id`` is the Xero
    ``ContactID`` (or, in stub mode, the deterministic stub id)."""

    xero_contact_id: str = Field(min_length=1, max_length=128)
    xero_contact_name: str | None = Field(default=None, max_length=255)
    xero_contact_email: str | None = Field(default=None, max_length=255)


# --- Admin: customers & users ------------------------------------------------

_CUSTOMER_STATUSES = {"active", "disabled"}
_ORDERING_USER_ROLES = {"owner", "buyer", "accounts", "viewer"}


class CustomerCreate(BaseModel):
    name: str = Field(min_length=1, max_length=255)
    client_code: str = Field(min_length=1, max_length=64)
    tenant_id: str | None = None
    notes: str | None = None


class CustomerUpdate(BaseModel):
    name: str | None = None
    status: str | None = None
    notes: str | None = None

    @field_validator("status")
    @classmethod
    def _check_status(cls, value: str | None) -> str | None:
        if value is not None and value not in _CUSTOMER_STATUSES:
            raise ValueError(f"status must be one of {sorted(_CUSTOMER_STATUSES)}")
        return value


class CustomerUserCreate(BaseModel):
    full_name: str = Field(min_length=1, max_length=255)
    email: str = Field(min_length=3, max_length=255)
    role: str = "buyer"

    @field_validator("role")
    @classmethod
    def _check_role(cls, value: str) -> str:
        if value not in _ORDERING_USER_ROLES:
            raise ValueError(f"role must be one of {sorted(_ORDERING_USER_ROLES)}")
        return value

    @field_validator("email")
    @classmethod
    def _normalize_email(cls, value: str) -> str:
        return value.strip().lower()


class CustomerUserUpdate(BaseModel):
    full_name: str | None = None
    role: str | None = None
    status: str | None = None

    @field_validator("role")
    @classmethod
    def _check_role(cls, value: str | None) -> str | None:
        if value is not None and value not in _ORDERING_USER_ROLES:
            raise ValueError(f"role must be one of {sorted(_ORDERING_USER_ROLES)}")
        return value

    @field_validator("status")
    @classmethod
    def _check_status(cls, value: str | None) -> str | None:
        if value is not None and value not in {"active", "invited", "suspended"}:
            raise ValueError("status must be one of ['active', 'invited', 'suspended']")
        return value
