"""Database models for persisted pricing configuration settings."""

from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING

from sqlalchemy import (
    JSON,
    DateTime,
    ForeignKey,
    Integer,
    Numeric,
    String,
    Text,
    UniqueConstraint,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
from sqlalchemy.sql import func

from config.database import Base
from services.currency import normalise_currency

if TYPE_CHECKING:  # pragma: no cover
    from .project import Project


class PricingSettings(Base):
    """Persisted pricing defaults applied to scenario evaluations."""

    __tablename__ = "pricing_settings"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    name: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
    slug: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    default_currency: Mapped[str | None] = mapped_column(
        String(3), nullable=True)
    default_payable_pct: Mapped[float] = mapped_column(
        Numeric(5, 2), nullable=False, default=100.0
    )
    moisture_threshold_pct: Mapped[float] = mapped_column(
        Numeric(5, 2), nullable=False, default=8.0
    )
    moisture_penalty_per_pct: Mapped[float] = mapped_column(
        Numeric(14, 4), nullable=False, default=0.0
    )
    metadata_payload: Mapped[dict | None] = mapped_column(
        "metadata", JSON, nullable=True
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
    )

    metal_overrides: Mapped[list["PricingMetalSettings"]] = relationship(
        "PricingMetalSettings",
        back_populates="pricing_settings",
        cascade="all, delete-orphan",
        passive_deletes=True,
    )
    impurity_overrides: Mapped[list["PricingImpuritySettings"]] = relationship(
        "PricingImpuritySettings",
        back_populates="pricing_settings",
        cascade="all, delete-orphan",
        passive_deletes=True,
    )
    projects: Mapped[list["Project"]] = relationship(
        "Project",
        back_populates="pricing_settings",
        cascade="all",
    )

    @validates("slug")
    def _normalise_slug(self, key: str, value: str) -> str:
        return value.strip().lower()

    @validates("default_currency")
    def _validate_currency(self, key: str, value: str | None) -> str | None:
        return normalise_currency(value)

    def __repr__(self) -> str:  # pragma: no cover
        return f"PricingSettings(id={self.id!r}, slug={self.slug!r})"


class PricingMetalSettings(Base):
    """Contract-specific overrides for a particular metal."""

    __tablename__ = "pricing_metal_settings"
    __table_args__ = (
        UniqueConstraint(
            "pricing_settings_id", "metal_code", name="uq_pricing_metal_settings_code"
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    pricing_settings_id: Mapped[int] = mapped_column(
        ForeignKey("pricing_settings.id", ondelete="CASCADE"), nullable=False, index=True
    )
    metal_code: Mapped[str] = mapped_column(String(32), nullable=False)
    payable_pct: Mapped[float | None] = mapped_column(
        Numeric(5, 2), nullable=True)
    moisture_threshold_pct: Mapped[float | None] = mapped_column(
        Numeric(5, 2), nullable=True)
    moisture_penalty_per_pct: Mapped[float | None] = mapped_column(
        Numeric(14, 4), nullable=True
    )
    data: Mapped[dict | None] = mapped_column(JSON, nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
    )

    pricing_settings: Mapped["PricingSettings"] = relationship(
        "PricingSettings", back_populates="metal_overrides"
    )

    @validates("metal_code")
    def _normalise_metal_code(self, key: str, value: str) -> str:
        return value.strip().lower()

    def __repr__(self) -> str:  # pragma: no cover
        return (
            "PricingMetalSettings("  # noqa: ISC001
            f"id={self.id!r}, pricing_settings_id={self.pricing_settings_id!r}, "
            f"metal_code={self.metal_code!r})"
        )


class PricingImpuritySettings(Base):
    """Impurity penalty thresholds associated with pricing settings."""

    __tablename__ = "pricing_impurity_settings"
    __table_args__ = (
        UniqueConstraint(
            "pricing_settings_id",
            "impurity_code",
            name="uq_pricing_impurity_settings_code",
        ),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    pricing_settings_id: Mapped[int] = mapped_column(
        ForeignKey("pricing_settings.id", ondelete="CASCADE"), nullable=False, index=True
    )
    impurity_code: Mapped[str] = mapped_column(String(32), nullable=False)
    threshold_ppm: Mapped[float] = mapped_column(
        Numeric(14, 4), nullable=False, default=0.0)
    penalty_per_ppm: Mapped[float] = mapped_column(
        Numeric(14, 4), nullable=False, default=0.0)
    notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now()
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()
    )

    pricing_settings: Mapped["PricingSettings"] = relationship(
        "PricingSettings", back_populates="impurity_overrides"
    )

    @validates("impurity_code")
    def _normalise_impurity_code(self, key: str, value: str) -> str:
        return value.strip().upper()

    def __repr__(self) -> str:  # pragma: no cover
        return (
            "PricingImpuritySettings("  # noqa: ISC001
            f"id={self.id!r}, pricing_settings_id={self.pricing_settings_id!r}, "
            f"impurity_code={self.impurity_code!r})"
        )
