"""进销存 Admin API 输入输出。"""
from decimal import Decimal
from typing import Literal

from pydantic import BaseModel, Field


class SettingsOut(BaseModel):
    lifecycle_state: str
    warehouse_mode: str
    cost_method: str
    batch_enabled: bool
    expiry_enabled: bool
    serial_enabled: bool
    qc_enabled: bool
    allow_negative: bool
    purchase_approval: bool
    period_close_enabled: bool
    base_currency: str
    default_warehouse_id: int | None


class EnableIn(BaseModel):
    warehouse_name: str = Field("主仓", max_length=120)
    warehouse_code: str = Field("MAIN", max_length=40)
    opening_mode: str = Field("from_stock", pattern="^(from_stock|zero)$")
    base_currency: str = Field("NZD", max_length=8)


class SettingsUpdateIn(BaseModel):
    lifecycle_state: str | None = Field(None, pattern="^(active|arrears|suspended|retired)$")
    cost_method: str | None = Field(None, pattern="^(moving_avg|fifo|batch_actual)$")
    batch_enabled: bool | None = None
    expiry_enabled: bool | None = None
    serial_enabled: bool | None = None
    qc_enabled: bool | None = None
    allow_negative: bool | None = None
    purchase_approval: bool | None = None
    period_close_enabled: bool | None = None
    default_warehouse_id: int | None = None


class ProductProfileIn(BaseModel):
    variant_id: int | None = None
    tracking_type: str = Field("none", pattern="^(none|lot|serial)$")
    qc_required: bool = False
    shelf_life_days: int | None = Field(None, ge=1, le=36500)
    removal_strategy: str = Field("fifo", pattern="^(fifo|fefo|manual)$")


class ProductProfileOut(ProductProfileIn):
    product_id: int


class InventoryPolicyIn(BaseModel):
    reserve_statuses: list[str]
    ship_statuses: list[str] = Field(..., min_length=1)
    release_statuses: list[str] = Field(..., min_length=1)
    status_sequence: list[str] = Field(..., min_length=1)

    def to_policy(self):
        from app.plugins.inventory.policy import InventoryPolicy
        return InventoryPolicy(
            reserve_statuses=frozenset(self.reserve_statuses),
            ship_statuses=frozenset(self.ship_statuses),
            release_statuses=frozenset(self.release_statuses),
            status_sequence=tuple(self.status_sequence),
        )


class WarehouseIn(BaseModel):
    code: str = Field(..., max_length=40)
    name: str = Field(..., max_length=120)


class WarehouseUpdateIn(WarehouseIn):
    is_active: bool


class WarehouseOut(BaseModel):
    id: int
    code: str
    name: str
    is_active: bool


class BalanceOut(BaseModel):
    product_id: int
    variant_id: int
    warehouse_id: int
    location_id: int
    batch_id: int
    stock_state: str
    qty: Decimal


class TransactionOut(BaseModel):
    id: int
    product_id: int
    variant_id: int | None
    warehouse_id: int
    stock_state: str
    qty_delta: Decimal
    qty_after: Decimal
    doc_type: str
    doc_id: int | None
    unallocated: bool
    created_at: str


class AdjustmentLineIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    warehouse_id: int | None = Field(None, description="未提供时使用默认仓库")
    batch_id: int | None = Field(None, description="启用批次管理时必须指定批次")
    qty_delta: Decimal = Field(..., description="正数增、负数减，不可为 0")
    reason: str = Field(..., max_length=255)


class AdjustmentIn(BaseModel):
    lines: list[AdjustmentLineIn] = Field(..., min_length=1)


class SupplierIn(BaseModel):
    code: str = Field(..., max_length=40)
    name: str = Field(..., max_length=160)
    currency: str = Field("NZD", max_length=8)
    contact: str | None = Field(None, max_length=120)
    email: str | None = Field(None, max_length=160)
    phone: str | None = Field(None, max_length=60)
    tax_id: str | None = Field(None, max_length=80)
    contacts: list[dict] = Field(default_factory=list)
    addresses: list[dict] = Field(default_factory=list)
    attachment_urls: list[str] = Field(default_factory=list)
    delivery_notes: str | None = None
    payment_notes: str | None = None


class SupplierUpdateIn(SupplierIn):
    is_active: bool


class SupplierProductIn(BaseModel):
    supplier_id: int
    product_id: int
    variant_id: int | None = None
    supplier_sku: str | None = Field(None, max_length=120)
    purchase_uom: str = Field("unit", max_length=40)
    uom_factor: Decimal = Field(Decimal("1"), gt=0)
    unit_price: Decimal = Field(Decimal("0"), ge=0)
    currency: str = Field("NZD", max_length=8)
    min_order_qty: Decimal = Field(Decimal("1"), gt=0)
    order_multiple: Decimal = Field(Decimal("1"), gt=0)
    lead_time_days: int = Field(0, ge=0, le=3650)
    priority: int = Field(100, ge=0, le=10000)
    valid_from: str | None = None
    valid_to: str | None = None
    is_active: bool = True


class PurchaseActionIn(BaseModel):
    comment: str | None = Field(None, max_length=500)
    reason: str | None = Field(None, max_length=255)


class POLineIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    qty: Decimal = Field(..., gt=0)
    unit_cost: Decimal = Field(..., ge=0)
    expected_date: str | None = None
    purchase_uom: str = Field("unit", max_length=40)
    uom_factor: Decimal = Field(Decimal("1"), gt=0)
    batch_no: str | None = Field(None, max_length=80)
    expires_on: str | None = None


class PurchaseOrderIn(BaseModel):
    supplier_id: int
    warehouse_id: int | None = None
    currency: str = Field("NZD", max_length=8)
    fx_rate: Decimal = Field(Decimal("1"), gt=0)
    supplier_promise_date: str | None = None
    planned_arrival_date: str | None = None
    lines: list[POLineIn] = Field(..., min_length=1)


class InventoryMoveIn(BaseModel):
    model_config = {"extra": "forbid"}

    product_id: int
    variant_id: int | None = None
    done_qty: Decimal = Field(..., gt=0)
    purchase_order_line_id: int | None = None
    source_location_id: int | None = None
    destination_location_id: int | None = None
    batch_id: int | None = None
    batch_no: str | None = Field(None, max_length=80)
    produced_on: str | None = None
    expires_on: str | None = None
    serial_numbers: list[str] = Field(default_factory=list)
    unit_cost: Decimal | None = Field(None, ge=0)


class PurchaseReceiptLineIn(BaseModel):
    model_config = {"extra": "forbid"}

    purchase_order_line_id: int
    done_qty: Decimal = Field(..., gt=0)
    destination_location_id: int | None = None
    batch_no: str | None = Field(None, max_length=80)
    produced_on: str | None = None
    expires_on: str | None = None
    serial_numbers: list[str] = Field(default_factory=list)


class PurchaseReceiptIn(BaseModel):
    purchase_order_id: int
    lines: list[PurchaseReceiptLineIn] = Field(..., min_length=1)
    planned_at: str | None = None
    reference: str | None = Field(None, max_length=120)
    idempotency_key: str | None = Field(None, max_length=120)


class VendorReturnIn(BaseModel):
    warehouse_id: int
    purchase_order_id: int | None = None
    lines: list[InventoryMoveIn] = Field(..., min_length=1)
    reason: str = Field(..., min_length=1, max_length=255)
    reference: str | None = Field(None, max_length=120)
    idempotency_key: str | None = Field(None, max_length=120)


class LocationIn(BaseModel):
    warehouse_id: int
    parent_id: int | None = None
    code: str = Field(..., min_length=1, max_length=40)
    name: str = Field(..., min_length=1, max_length=120)
    location_type: str = Field("internal", pattern="^(internal|input|output|transit|qc|scrap)$")
    barcode: str | None = Field(None, max_length=80)
    is_default: bool = False


class LocationUpdateIn(LocationIn):
    is_active: bool = True


class PutawayRuleIn(BaseModel):
    warehouse_id: int
    product_id: int | None = None
    category_id: int | None = None
    destination_location_id: int
    priority: int = Field(100, ge=0, le=10000)
    is_active: bool = True


class TransferOperationIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    source_warehouse_id: int
    destination_warehouse_id: int
    source_location_id: int | None = None
    destination_location_id: int | None = None
    qty: Decimal = Field(..., gt=0)
    batch_id: int | None = None
    serial_numbers: list[str] = Field(default_factory=list)
    steps: Literal[1, 3] = 1
    reference: str | None = Field(None, max_length=120)
    idempotency_key: str | None = Field(None, max_length=120)


class AllocationRequestIn(BaseModel):
    warehouse_id: int | None = None
    shortage_mode: Literal["reject", "partial", "backorder"] = "reject"


class FulfillmentLineIn(BaseModel):
    order_item_id: int
    qty: Decimal = Field(..., gt=0)


class FulfillmentActionIn(BaseModel):
    lines: list[FulfillmentLineIn] = Field(default_factory=list)


class CustomerReturnLineIn(BaseModel):
    order_item_id: int
    qty: Decimal = Field(..., gt=0)
    disposition: Literal["resellable", "inspect", "scrap"] = "inspect"


class CustomerReturnIn(BaseModel):
    lines: list[CustomerReturnLineIn] = Field(..., min_length=1)
    reason: str = Field(..., min_length=1, max_length=255)
    reference: str | None = Field(None, max_length=120)
    idempotency_key: str | None = Field(None, max_length=120)


class StockStateActionIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    warehouse_id: int
    location_id: int
    batch_id: int | None = None
    qty: Decimal = Field(..., gt=0)
    from_state: Literal["sellable", "qc", "inspection", "frozen"]
    to_state: Literal["sellable", "qc", "inspection", "frozen", "scrapped"]
    serial_numbers: list[str] = Field(default_factory=list)
    reason: str = Field(..., min_length=1, max_length=255)
    reference: str | None = Field(None, max_length=120)
    idempotency_key: str | None = Field(None, max_length=120)


class ReorderRuleIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    warehouse_id: int
    supplier_id: int | None = None
    min_qty: Decimal = Field(..., ge=0)
    target_qty: Decimal = Field(..., ge=0)
    safety_qty: Decimal = Field(0, ge=0)
    min_order_qty: Decimal = Field(1, gt=0)
    order_multiple: Decimal = Field(1, gt=0)
    is_enabled: bool = True


class LandedCostIn(BaseModel):
    receipt_operation_id: int
    total_cost: Decimal = Field(..., gt=0)
    allocation_method: Literal["quantity", "weight", "value"] = "value"
    reference: str | None = Field(None, max_length=120)
    idempotency_key: str = Field(..., min_length=1, max_length=120)


class SuggestionConvertIn(BaseModel):
    suggestion_ids: list[int] = Field(..., min_length=1)


class RevaluationIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    new_unit_cost: Decimal = Field(..., ge=0)
    reason: str = Field(..., min_length=1, max_length=255)
    idempotency_key: str = Field(..., min_length=1, max_length=120)


class TransferIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    from_warehouse_id: int
    to_warehouse_id: int
    qty: Decimal = Field(..., gt=0)
    idempotency_key: str | None = Field(None, max_length=80, description="客户端幂等键，超时重试传同值去重")


class StocktakeIn(BaseModel):
    product_id: int
    variant_id: int | None = None
    warehouse_id: int | None = Field(None, description="未提供时使用默认仓库")
    batch_id: int | None = Field(None, description="启用批次管理时必须指定批次")
    counted_qty: Decimal = Field(..., ge=0)
    idempotency_key: str | None = Field(None, max_length=80)


class BatchQCIn(BaseModel):
    qc_state: str = Field(..., pattern="^(passed|hold|rejected)$")


class KitOpIn(BaseModel):
    parent_product_id: int
    kit_qty: Decimal = Field(..., gt=0)
    disassemble: bool = False
    idempotency_key: str | None = Field(None, max_length=80)
