from datetime import datetime

from pydantic import BaseModel, ConfigDict, Field


class EditorProductRow(BaseModel):
    id: int
    tenant_id: str
    client_name: str
    item_id: str | None
    name: str
    mix_id: int
    mix_client_name: str
    mix_name: str
    sale_type: str
    unit_of_measure: str
    visible: bool
    product_notes: str | None
    mix_notes: str | None


class EditorProductUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    client_name: str | None = Field(default=None, min_length=1, max_length=255)
    name: str | None = Field(default=None, min_length=1, max_length=255)
    item_id: str | None = Field(default=None, max_length=128)
    mix_id: int | None = None
    sale_type: str | None = Field(default=None, min_length=1, max_length=64)
    unit_of_measure: str | None = Field(default=None, min_length=1, max_length=64)
    visible: bool | None = None
    notes: str | None = Field(default=None, max_length=2000)


class EditorMixCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    client_name: str = Field(min_length=1, max_length=255)
    name: str = Field(min_length=1, max_length=255)
    notes: str | None = Field(default=None, max_length=2000)


class EditorMixUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    client_name: str | None = Field(default=None, min_length=1, max_length=255)
    name: str | None = Field(default=None, min_length=1, max_length=255)
    notes: str | None = Field(default=None, max_length=2000)
    # Toggling a mix's status fans out to the visibility of every product under it.
    visible: bool | None = None


class EditorMixRow(BaseModel):
    id: int
    tenant_id: str
    client_name: str
    name: str
    # A mix reads as "Active" when at least one of its products is visible.
    visible: bool
    product_count: int
    visible_product_count: int
    notes: str | None


class EditorMixIngredientRead(BaseModel):
    id: int
    raw_material_id: int
    raw_material_name: str
    quantity_kg: float
    notes: str | None


class EditorMixFormulaRead(BaseModel):
    id: int
    tenant_id: str
    client_name: str
    name: str
    ingredients: list[EditorMixIngredientRead]
    total_kg: float


class EditorResolvedMixIngredient(BaseModel):
    raw_material_id: int
    raw_material_name: str
    quantity_kg: float
    # This row's share of the mix total, matching the Mix Calculator.
    mix_percentage: float
    unit: str
    notes: str | None


class EditorResolvedMixFormula(BaseModel):
    """A mix formula resolved the way the Mix Calculator reads it.

    `source` is `product` when the numbers come from a representative product's
    own formula, or `mix` when they come from the shared mix master fallback.
    The PUT endpoint writes back to whichever source produced these rows.
    """

    id: int
    tenant_id: str
    client_name: str
    name: str
    source: str
    product_id: int | None
    ingredients: list[EditorResolvedMixIngredient]
    total_kg: float


class EditorMixFormulaRowInput(BaseModel):
    model_config = ConfigDict(extra="forbid")

    raw_material_id: int
    quantity_kg: float = Field(gt=0)
    notes: str | None = Field(default=None, max_length=1000)


class EditorMixFormulaReplace(BaseModel):
    """Full replacement of a mix's formula in one save.

    The frontend keeps kilograms as the canonical value (percentages are an
    entry aid that resolve back to kg against the total), so the API only needs
    the resolved kg per row.
    """

    model_config = ConfigDict(extra="forbid")

    rows: list[EditorMixFormulaRowInput] = Field(min_length=1)


class EditorMixIngredientCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    raw_material_id: int
    quantity_kg: float = Field(gt=0)
    notes: str | None = Field(default=None, max_length=1000)


class EditorMixIngredientUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    quantity_kg: float | None = Field(default=None, gt=0)
    notes: str | None = Field(default=None, max_length=1000)


class EditorProductIngredientCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    raw_material_id: int
    quantity_kg: float = Field(gt=0)
    notes: str | None = Field(default=None, max_length=1000)


class EditorProductIngredientUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    quantity_kg: float | None = Field(default=None, gt=0)
    notes: str | None = Field(default=None, max_length=1000)


class EditorProductIngredientRead(BaseModel):
    id: int
    raw_material_id: int
    raw_material_name: str
    quantity_kg: float
    sort_order: int
    notes: str | None


class EditorProductFormulaRead(BaseModel):
    id: int
    tenant_id: str
    client_name: str
    name: str
    mix_id: int
    mix_name: str
    ingredients: list[EditorProductIngredientRead]
    total_kg: float


# --- Ingredients (raw materials) catalogue -----------------------------------


class EditorIngredientRow(BaseModel):
    id: int
    name: str
    supplier: str | None
    unit_of_measure: str
    kg_per_unit: float
    status: str
    # Decimal places this ingredient is rounded to in the mix calculator output.
    rounding_decimals: int
    notes: str | None
    cost_per_kg: float | None
    # How many product/mix formulas currently reference this ingredient.
    usage_count: int
    created_at: datetime


class EditorIngredientCreate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str = Field(min_length=1, max_length=255)
    supplier: str | None = Field(default=None, max_length=255)
    unit_of_measure: str = Field(min_length=1, max_length=64)
    kg_per_unit: float = Field(gt=0)
    status: str = Field(default="active", max_length=32)
    rounding_decimals: int = Field(default=2, ge=0, le=6)
    notes: str | None = Field(default=None, max_length=2000)


class EditorIngredientUpdate(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str | None = Field(default=None, min_length=1, max_length=255)
    supplier: str | None = Field(default=None, max_length=255)
    unit_of_measure: str | None = Field(default=None, min_length=1, max_length=64)
    kg_per_unit: float | None = Field(default=None, gt=0)
    status: str | None = Field(default=None, max_length=32)
    rounding_decimals: int | None = Field(default=None, ge=0, le=6)
    notes: str | None = Field(default=None, max_length=2000)


# --- Change history ----------------------------------------------------------


class EditorChangeFieldDelta(BaseModel):
    field: str
    label: str
    before: str | None = None
    after: str | None = None


class EditorChangeEventRead(BaseModel):
    id: int
    entity_type: str
    entity_id: int
    action: str
    actor_name: str
    actor_email: str
    actor_role: str | None
    summary: str
    changes: list[EditorChangeFieldDelta]
    created_at: datetime
