from __future__ import annotations

from datetime import date, datetime
from typing import TYPE_CHECKING, List

from sqlalchemy import (
    Date,
    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
from .enums import ResourceType, ScenarioStatus, sql_enum
from .profitability_snapshot import ScenarioProfitability
from .capex_snapshot import ScenarioCapexSnapshot
from .opex_snapshot import ScenarioOpexSnapshot

if TYPE_CHECKING:  # pragma: no cover
    from .financial_input import FinancialInput
    from .project import Project
    from .simulation_parameter import SimulationParameter


class Scenario(Base):
    """A specific configuration of assumptions for a project."""

    __tablename__ = "scenarios"
    __table_args__ = (
        UniqueConstraint("project_id", "name",
                         name="uq_scenarios_project_name"),
    )

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    project_id: Mapped[int] = mapped_column(
        ForeignKey("projects.id", ondelete="CASCADE"), nullable=False, index=True
    )
    name: Mapped[str] = mapped_column(String(255), nullable=False)
    description: Mapped[str | None] = mapped_column(Text, nullable=True)
    status: Mapped[ScenarioStatus] = mapped_column(
        sql_enum(ScenarioStatus, name="scenariostatus"),
        nullable=False,
        default=ScenarioStatus.DRAFT,
    )
    start_date: Mapped[date | None] = mapped_column(Date, nullable=True)
    end_date: Mapped[date | None] = mapped_column(Date, nullable=True)
    discount_rate: Mapped[float | None] = mapped_column(
        Numeric(5, 2), nullable=True)
    currency: Mapped[str | None] = mapped_column(String(3), nullable=True)
    primary_resource: Mapped[ResourceType | None] = mapped_column(
        sql_enum(ResourceType, name="resourcetype"), 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()
    )

    project: Mapped["Project"] = relationship(
        "Project", back_populates="scenarios")
    financial_inputs: Mapped[List["FinancialInput"]] = relationship(
        "FinancialInput",
        back_populates="scenario",
        cascade="all, delete-orphan",
        passive_deletes=True,
    )
    simulation_parameters: Mapped[List["SimulationParameter"]] = relationship(
        "SimulationParameter",
        back_populates="scenario",
        cascade="all, delete-orphan",
        passive_deletes=True,
    )
    profitability_snapshots: Mapped[List["ScenarioProfitability"]] = relationship(
        "ScenarioProfitability",
        back_populates="scenario",
        cascade="all, delete-orphan",
        order_by=lambda: ScenarioProfitability.calculated_at.desc(),
        passive_deletes=True,
    )
    capex_snapshots: Mapped[List["ScenarioCapexSnapshot"]] = relationship(
        "ScenarioCapexSnapshot",
        back_populates="scenario",
        cascade="all, delete-orphan",
        order_by=lambda: ScenarioCapexSnapshot.calculated_at.desc(),
        passive_deletes=True,
    )
    opex_snapshots: Mapped[List["ScenarioOpexSnapshot"]] = relationship(
        "ScenarioOpexSnapshot",
        back_populates="scenario",
        cascade="all, delete-orphan",
        order_by=lambda: ScenarioOpexSnapshot.calculated_at.desc(),
        passive_deletes=True,
    )

    @validates("currency")
    def _normalise_currency(self, key: str, value: str | None) -> str | None:
        # Normalise to uppercase ISO-4217; raises when the code is malformed.
        return normalise_currency(value)

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

    @property
    def latest_profitability(self) -> "ScenarioProfitability | None":
        """Return the most recent profitability snapshot for this scenario."""

        if not self.profitability_snapshots:
            return None
        return self.profitability_snapshots[0]

    @property
    def latest_capex(self) -> "ScenarioCapexSnapshot | None":
        """Return the most recent capex snapshot for this scenario."""

        if not self.capex_snapshots:
            return None
        return self.capex_snapshots[0]

    @property
    def latest_opex(self) -> "ScenarioOpexSnapshot | None":
        """Return the most recent opex snapshot for this scenario."""

        if not self.opex_snapshots:
            return None
        return self.opex_snapshots[0]
