from __future__ import annotations

from datetime import date, datetime
from decimal import Decimal
from typing import Any, Literal
from uuid import UUID

from pydantic import BaseModel, Field, ConfigDict, field_validator


class ApiModel(BaseModel):
    model_config = ConfigDict(extra='forbid')


class PartyIn(ApiModel):
    display_name: str
    party_type: Literal['own_company','supplier','customer','channel','warehouse','person','platform','other'] = 'other'
    legal_name: str | None = None
    short_name: str | None = None
    tax_no: str | None = None
    address: str | None = None
    contact_person: str | None = None
    phone_masked: str | None = None
    email_masked: str | None = None
    notes: str | None = None


class ProductIn(ApiModel):
    standard_name: str
    sku_code: str | None = None
    raw_name_default: str | None = None
    category: str | None = None
    spec: str | None = None
    default_unit: str | None = None
    barcode_69: str | None = None
    guobo_code: str | None = None
    brand: str | None = None
    series: str | None = None
    model: str | None = None
    color_style: str | None = None
    package_spec: str | None = None
    tax_rate: Decimal | None = None
    list_price: Decimal | None = None
    cost_price: Decimal | None = None
    notes: str | None = None


class SourceFileIn(ApiModel):
    original_filename: str
    stored_path: str | None = None
    source_cache_path: str | None = None
    sha256: str | None = None
    byte_size: int | None = None
    mime_type: str | None = None
    material_type: Literal['contract','purchase_order','supplemental_demand','shipment_signoff','ledger','extraction_csv','image','other'] = 'other'
    received_at: datetime | None = None
    source_platform: str | None = None
    uploader: str | None = None
    received_status: str | None = None
    original_source_path: str | None = None
    file_exists: bool | None = None
    notes: str | None = None


class ChannelAllocationIn(ApiModel):
    standard_channel: str
    raw_channel: str | None = None
    channel_party_id: UUID | None = None
    original_quantity: Decimal = Decimal('0')
    adjustment_quantity: Decimal = Decimal('0')
    allocation_type: str | None = 'planned'
    planned_quantity: Decimal | None = None
    confirmed_quantity: Decimal | None = None
    shipped_quantity: Decimal | None = None
    signed_quantity: Decimal | None = None
    adjustment_reason: str | None = None
    evidence_note: str | None = None
    notes: str | None = None

    @field_validator('original_quantity','adjustment_quantity','planned_quantity','confirmed_quantity','shipped_quantity','signed_quantity')
    @classmethod
    def non_negative_or_none(cls, v):
        if v is not None and v < 0:
            raise ValueError('quantity cannot be negative')
        return v


class OrderLineIn(ApiModel):
    product_id: UUID | None = None
    product_name_raw: str
    product_name_standard_draft: str | None = None
    quantity: Decimal
    unit: str | None = None
    unit_price: Decimal | None = None
    amount: Decimal | None = None
    material: str | None = None
    line_external_no: str | None = None
    product_code_raw: str | None = None
    barcode_69_raw: str | None = None
    spec_raw: str | None = None
    color_style: str | None = None
    package_spec: str | None = None
    tax_rate: Decimal | None = None
    delivery_quantity: Decimal | None = None
    received_quantity: Decimal | None = None
    returned_quantity: Decimal | None = None
    arrival_status: str | None = None
    expected_arrival_start: date | None = None
    expected_arrival_end: date | None = None
    evidence_note: str | None = None
    notes: str | None = None
    channel_allocations: list[ChannelAllocationIn] = Field(default_factory=list)

    @field_validator('quantity','unit_price','amount','delivery_quantity','received_quantity','returned_quantity')
    @classmethod
    def non_negative_or_none(cls, v):
        if v is not None and v < 0:
            raise ValueError('numeric value cannot be negative')
        return v


class PurchaseOrderIn(ApiModel):
    project_id: UUID | None = None
    document_id: UUID | None = None
    order_side: Literal['downstream_supplier_purchase','upstream_customer_order','internal_transfer','supplemental_demand']
    order_type: Literal['formal_order','contract_order','no_document_supplement','replenishment','adjustment'] = 'formal_order'
    buyer_party_id: UUID | None = None
    seller_party_id: UUID | None = None
    order_no: str | None = None
    order_date: date | None = None
    external_contract_no: str | None = None
    supplier_contract_no_on_order: str | None = None
    customer_contract_no_on_order: str | None = None
    settlement_method: str | None = None
    expected_arrival_start: date | None = None
    expected_arrival_end: date | None = None
    expected_arrival_text: str | None = None
    delivery_address: str | None = None
    receiver_name: str | None = None
    receiver_phone_masked: str | None = None
    logistics_company: str | None = None
    tracking_no: str | None = None
    payment_status: str | None = None
    fulfillment_status: str | None = None
    notes: str | None = None
    lines: list[OrderLineIn] = Field(default_factory=list)


class StockMovementIn(ApiModel):
    stock_item_id: UUID
    movement_date: date
    direction: Literal['in','out']
    movement_type: Literal['purchase_in','self_stock_in','shipment_out','claim_out','return_in','gift_out','transfer_out','transfer_in','adjustment','scrap_out']
    quantity: Decimal
    unit: str | None = None
    handler_party_id: UUID | None = None
    counterparty_id: UUID | None = None
    purpose: str | None = None
    returnable: bool | None = None
    unit_cost_tax_included: Decimal | None = None
    source_document_id: UUID | None = None
    evidence_file_id: UUID | None = None
    source_order_line_id: UUID | None = None
    expected_return_date: date | None = None
    actual_return_date: date | None = None
    signoff_status: str | None = None
    logistics_company: str | None = None
    tracking_no: str | None = None
    delivery_address: str | None = None
    receiver_name: str | None = None
    receiver_phone_masked: str | None = None
    notes: str | None = None

    @field_validator('quantity','unit_cost_tax_included')
    @classmethod
    def non_negative_or_none(cls, v):
        if v is not None and v < 0:
            raise ValueError('numeric value cannot be negative')
        return v


class ShipmentLineIn(ApiModel):
    product_id: UUID | None = None
    product_name: str
    quantity: Decimal
    unit: str | None = None
    stock_movement_id: UUID | None = None
    sign_status: Literal['pending','signed','rejected'] = 'pending'
    shipped_quantity: Decimal | None = None
    signed_quantity: Decimal | None = None
    rejected_quantity: Decimal | None = None
    rejection_reason: str | None = None
    notes: str | None = None


class ShipmentSignoffIn(ApiModel):
    project_id: UUID | None = None
    shipper_party_id: UUID | None = None
    receiver_party_id: UUID | None = None
    ship_date: date | None = None
    purpose: str | None = None
    status: Literal['draft','sent','signed','cancelled'] = 'draft'
    source_document_id: UUID | None = None
    logistics_company: str | None = None
    tracking_no: str | None = None
    delivery_address: str | None = None
    receiver_name: str | None = None
    receiver_phone_masked: str | None = None
    expected_arrival_start: date | None = None
    expected_arrival_end: date | None = None
    signed_at: datetime | None = None
    signer_name: str | None = None
    signer_contact_masked: str | None = None
    notes: str | None = None
    lines: list[ShipmentLineIn] = Field(default_factory=list)


class ValidationIssueIn(ApiModel):
    issue_type: Literal['missing_field','conflict','quantity_mismatch','channel_mismatch','duplicate_file','unknown_product','unknown_party','negative_stock','other'] = 'other'
    severity: Literal['info','warning','error'] = 'warning'
    related_table: str | None = None
    related_id: UUID | None = None
    field_name: str | None = None
    message: str
    source_file_id: UUID | None = None


class ReconciliationRunIn(ApiModel):
    project_id: UUID | None = None
    notes: str | None = None


class WriteEnvelope(ApiModel):
    actor_profile: str = 'unknown'
    idempotency_key: str | None = None
    payload: dict[str, Any]
