"""Canonical tool-input models — Pydantic contracts for CSV and API data.

These models define what data any external system (CSV, API, direct construction)
needs to provide to work with Platoon's tools. They are the data contract between
raw input and the learning modules.

Unlike platoon.data.schema (Shopify sync-layer models with UUID/Decimal types),
these are lightweight tool-input contracts with str→float/int/date coercion
and field-level validation.

Usage:
    from platoon.data.models import DemandRecord, StaffMember

    # Single row from csv.DictReader
    record = DemandRecord.from_csv_row({"date": "2024-01-15", "product_id": "P1", "units_sold": "42"})

    # Batch validation with error collection
    try:
        records = ProductRecord.validate_csv(rows)
    except ValueError as e:
        print(e.errors)        # list of per-row errors
        print(e.valid_records)  # successfully parsed rows
"""

from __future__ import annotations

import datetime as dt
import json
from typing import Any, ClassVar, List, Optional

from pydantic import BaseModel, Field, field_validator


# ---------------------------------------------------------------------------
# Base class
# ---------------------------------------------------------------------------


class ToolRecord(BaseModel):
    """Base for all tool-input models.

    Provides CSV-row coercion and batch validation with error collection.
    """

    # Subclasses list fields that hold comma-separated or JSON lists in CSV
    _list_fields: ClassVar[list[str]] = []

    @classmethod
    def from_csv_row(cls, row: dict[str, str]) -> "ToolRecord":
        """Coerce a csv.DictReader row (all strings) into a typed model.

        Handles:
        - str→float/int/date via Pydantic v2 native coercion
        - list[str] fields: comma-separated ("a,b,c") or JSON array ('["a","b"]')
        - bool fields: "True"/"False"/"1"/"0"/""
        """
        cleaned: dict[str, Any] = {}
        for key, value in row.items():
            if key in cls._list_fields:
                cleaned[key] = _parse_list(value)
            elif value == "":
                cleaned[key] = None
            else:
                cleaned[key] = value
        return cls.model_validate(cleaned)

    @classmethod
    def validate_csv(cls, rows: list[dict[str, str]]) -> list["ToolRecord"]:
        """Batch-validate CSV rows, collecting errors.

        Returns list of valid records on success.
        Raises ValueError with .errors and .valid_records attributes when
        any rows fail validation.
        """
        valid: list[ToolRecord] = []
        errors: list[dict[str, Any]] = []
        for i, row in enumerate(rows):
            try:
                valid.append(cls.from_csv_row(row))
            except Exception as e:
                errors.append({"row": i, "data": row, "error": str(e)})
        if errors:
            exc = ValueError(
                f"{len(errors)} of {len(rows)} rows failed validation. "
                f"First error (row {errors[0]['row']}): {errors[0]['error']}"
            )
            exc.errors = errors  # type: ignore[attr-defined]
            exc.valid_records = valid  # type: ignore[attr-defined]
            raise exc
        return valid


def _parse_list(value: Any) -> list[str]:
    """Parse a CSV cell into list[str].

    Supports:
    - None / empty string → []
    - JSON array: '["a","b"]' → ["a", "b"]
    - Comma-separated: "a,b,c" → ["a", "b", "c"]
    - Already a list → passthrough
    """
    if value is None or value == "":
        return []
    if isinstance(value, list):
        return value
    value = str(value).strip()
    if value.startswith("["):
        try:
            parsed = json.loads(value)
            if isinstance(parsed, list):
                return [str(v).strip() for v in parsed]
        except (json.JSONDecodeError, ValueError):
            pass
    return [v.strip() for v in value.split(",") if v.strip()]


# ---------------------------------------------------------------------------
# Product
# ---------------------------------------------------------------------------


class ProductRecord(ToolRecord):
    """A product in the catalog — input for optimize and cashflow tools.

    CSV: products.csv
    """

    product_id: str = Field(description="Unique product identifier (e.g. PROD-1001)")
    sku: str = Field(description="Stock-keeping unit code")
    title: str = Field(default="", description="Product display name")
    category: str = Field(default="", description="Product category")
    price: float = Field(gt=0, description="Selling price per unit")
    cost: float = Field(gt=0, description="Cost per unit (COGS)")
    base_daily_demand: float = Field(ge=0, description="Average daily units sold")
    lead_time_days: int = Field(ge=1, description="Supplier lead time in days")
    weight_kg: Optional[float] = Field(default=None, ge=0, description="Product weight in kg")
    status: str = Field(default="active", description="Product status: active, draft, archived")
    intermittent: bool = Field(default=False, description="True if demand is sporadic (many zero days)")
    initial_stock: int = Field(default=0, ge=0, description="Starting inventory quantity")

    @field_validator("intermittent", mode="before")
    @classmethod
    def _coerce_bool(cls, v: Any) -> bool:
        if isinstance(v, str):
            return v.lower() in ("true", "1", "yes")
        return bool(v)


# ---------------------------------------------------------------------------
# Demand
# ---------------------------------------------------------------------------


class DemandRecord(ToolRecord):
    """Daily demand observation — input for forecast, detect_anomalies, schedule.

    CSV: daily_demand.csv
    """

    date: dt.date = Field(description="Observation date (YYYY-MM-DD)")
    product_id: str = Field(description="Product identifier")
    sku: str = Field(default="", description="SKU code")
    category: str = Field(default="", description="Product category")
    units_sold: int = Field(ge=0, description="Units sold on this date")

    @field_validator("date", mode="before")
    @classmethod
    def _parse_date(cls, v: Any) -> dt.date:
        if isinstance(v, dt.datetime):
            return v.date()
        if isinstance(v, dt.date):
            return v
        return dt.datetime.strptime(str(v), "%Y-%m-%d").date()


# ---------------------------------------------------------------------------
# Orders
# ---------------------------------------------------------------------------


class OrderLine(ToolRecord):
    """A single line item from an order — input for basket_analysis and optimize (ABC).

    CSV: orders.csv
    """

    order_id: str = Field(description="Order identifier")
    customer_id: str = Field(default="", description="Customer identifier")
    order_date: str = Field(default="", description="Order date (YYYY-MM-DD)")
    product_id: str = Field(description="Product identifier")
    quantity: int = Field(default=1, ge=0, description="Quantity ordered")
    unit_price: float = Field(default=0.0, ge=0, description="Price per unit at time of sale")
    line_total: float = Field(default=0.0, ge=0, description="Total for this line (qty * price - discount)")
    discount: float = Field(default=0.0, ge=0, description="Discount amount applied")
    financial_status: str = Field(default="paid", description="Payment status: paid, refunded, etc.")
    fulfillment_status: str = Field(default="", description="Fulfillment status: fulfilled, partial, etc.")
    currency: str = Field(default="USD", description="Currency code")


# ---------------------------------------------------------------------------
# Inventory
# ---------------------------------------------------------------------------


class InventorySnapshot(ToolRecord):
    """Daily inventory snapshot per product — input for optimize and cashflow.

    CSV: inventory.csv
    """

    date: dt.date = Field(description="Snapshot date (YYYY-MM-DD)")
    product_id: str = Field(description="Product identifier")
    opening_stock: int = Field(ge=0, description="Stock at start of day")
    units_sold: int = Field(ge=0, description="Units sold during the day")
    units_received: int = Field(default=0, ge=0, description="Units received from suppliers")
    closing_stock: int = Field(ge=0, description="Stock at end of day")
    cost_per_unit: float = Field(default=0.0, ge=0, description="Unit cost for valuation")
    stock_value: float = Field(default=0.0, ge=0, description="closing_stock * cost_per_unit")

    @field_validator("date", mode="before")
    @classmethod
    def _parse_date(cls, v: Any) -> dt.date:
        if isinstance(v, dt.datetime):
            return v.date()
        if isinstance(v, dt.date):
            return v
        return dt.datetime.strptime(str(v), "%Y-%m-%d").date()


# ---------------------------------------------------------------------------
# Customers
# ---------------------------------------------------------------------------


class CustomerRecord(ToolRecord):
    """Customer record — input for analytics (RFM, LTV, cohort).

    CSV: customers.csv
    """

    customer_id: str = Field(description="Unique customer identifier")
    email: str = Field(default="", description="Customer email address")
    first_name: str = Field(default="", description="First name")
    last_name: str = Field(default="", description="Last name")
    country: str = Field(default="", description="Country code (e.g. US, UK)")
    created_at: str = Field(default="", description="Account creation date (YYYY-MM-DD)")


# ---------------------------------------------------------------------------
# Staff
# ---------------------------------------------------------------------------


class StaffMember(ToolRecord):
    """Staff member — input for schedule tool.

    CSV: staff.csv
    """

    _list_fields: ClassVar[list[str]] = ["available_days", "skills"]

    staff_id: str = Field(description="Unique staff identifier")
    name: str = Field(description="Staff member name")
    hourly_rate: float = Field(gt=0, description="Hourly pay rate")
    max_hours_per_week: float = Field(default=40, gt=0, description="Maximum hours per week")
    available_days: list[str] = Field(
        default_factory=list,
        description="Days available to work (e.g. Monday, Tuesday)",
    )
    staff_type: str = Field(default="full_time", description="Employment type: full_time, part_time, weekend")
    skills: list[str] = Field(
        default_factory=list,
        description="Skill tags (e.g. math, physics, AP_calc). In CSV: comma-separated.",
    )


# ---------------------------------------------------------------------------
# Demand slots (for skill-based scheduling)
# ---------------------------------------------------------------------------


class DemandSlot(ToolRecord):
    """A demand slot with optional skill requirements — input for skill-based scheduling.

    CSV: tutor_slots.csv (or any slots file)
    """

    _list_fields: ClassVar[list[str]] = ["required_skills"]

    slot_label: str = Field(description="Slot identifier (e.g. Monday_math, Tuesday_science)")
    hours_needed: float = Field(gt=0, description="Hours of coverage needed for this slot")
    required_skills: list[str] = Field(
        default_factory=list,
        description="Skills required to fill this slot. Empty = any staff qualifies.",
    )
