from __future__ import annotations

from datetime import date, datetime
from typing import Any, Mapping
from typing import Literal

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator

from models import MiningOperationType, ResourceType, ScenarioStatus
from services.currency import CurrencyValidationError, normalise_currency

PreviewStateLiteral = Literal["new", "update", "skip", "error"]


def _normalise_string(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, str):
        return value.strip()
    return str(value).strip()


def _strip_or_none(value: Any | None) -> str | None:
    if value is None:
        return None
    text = _normalise_string(value)
    return text or None


def _coerce_enum(value: Any, enum_cls: Any, aliases: Mapping[str, Any]) -> Any:
    if value is None:
        return value
    if isinstance(value, enum_cls):
        return value
    text = _normalise_string(value).lower()
    if not text:
        return None
    if text in aliases:
        return aliases[text]
    try:
        return enum_cls(text)
    except ValueError as exc:  # pragma: no cover - surfaced by Pydantic
        raise ValueError(
            f"Invalid value '{value}' for {enum_cls.__name__}") from exc


OPERATION_TYPE_ALIASES: dict[str, MiningOperationType] = {
    "open pit": MiningOperationType.OPEN_PIT,
    "openpit": MiningOperationType.OPEN_PIT,
    "underground": MiningOperationType.UNDERGROUND,
    "in-situ leach": MiningOperationType.IN_SITU_LEACH,
    "in situ": MiningOperationType.IN_SITU_LEACH,
    "placer": MiningOperationType.PLACER,
    "quarry": MiningOperationType.QUARRY,
    "mountaintop removal": MiningOperationType.MOUNTAINTOP_REMOVAL,
    "other": MiningOperationType.OTHER,
}


SCENARIO_STATUS_ALIASES: dict[str, ScenarioStatus] = {
    "draft": ScenarioStatus.DRAFT,
    "active": ScenarioStatus.ACTIVE,
    "archived": ScenarioStatus.ARCHIVED,
}


RESOURCE_TYPE_ALIASES: dict[str, ResourceType] = {
    key.replace("_", " ").lower(): value for key, value in ResourceType.__members__.items()
}
RESOURCE_TYPE_ALIASES.update(
    {value.value.replace("_", " ").lower(): value for value in ResourceType}
)


class ProjectImportRow(BaseModel):
    name: str
    location: str | None = None
    operation_type: MiningOperationType
    description: str | None = None
    created_at: datetime | None = None
    updated_at: datetime | None = None

    model_config = ConfigDict(extra="forbid")

    @field_validator("name", mode="before")
    @classmethod
    def validate_name(cls, value: Any) -> str:
        text = _normalise_string(value)
        if not text:
            raise ValueError("Project name is required")
        return text

    @field_validator("location", "description", mode="before")
    @classmethod
    def optional_text(cls, value: Any | None) -> str | None:
        return _strip_or_none(value)

    @field_validator("operation_type", mode="before")
    @classmethod
    def map_operation_type(cls, value: Any) -> MiningOperationType | None:
        return _coerce_enum(value, MiningOperationType, OPERATION_TYPE_ALIASES)


class ScenarioImportRow(BaseModel):
    project_name: str
    name: str
    status: ScenarioStatus = ScenarioStatus.DRAFT
    start_date: date | None = None
    end_date: date | None = None
    discount_rate: float | None = None
    currency: str | None = None
    primary_resource: ResourceType | None = None
    description: str | None = None
    created_at: datetime | None = None
    updated_at: datetime | None = None

    model_config = ConfigDict(extra="forbid")

    @field_validator("project_name", "name", mode="before")
    @classmethod
    def validate_required_text(cls, value: Any, info) -> str:
        text = _normalise_string(value)
        if not text:
            raise ValueError(
                f"{info.field_name.replace('_', ' ').title()} is required")
        return text

    @field_validator("status", mode="before")
    @classmethod
    def map_status(cls, value: Any) -> ScenarioStatus | None:
        return _coerce_enum(value, ScenarioStatus, SCENARIO_STATUS_ALIASES)

    @field_validator("primary_resource", mode="before")
    @classmethod
    def map_resource(cls, value: Any) -> ResourceType | None:
        return _coerce_enum(value, ResourceType, RESOURCE_TYPE_ALIASES)

    @field_validator("description", mode="before")
    @classmethod
    def optional_description(cls, value: Any | None) -> str | None:
        return _strip_or_none(value)

    @field_validator("currency", mode="before")
    @classmethod
    def normalise_currency(cls, value: Any | None) -> str | None:
        text = _strip_or_none(value)
        if text is None:
            return None
        try:
            return normalise_currency(text)
        except CurrencyValidationError as exc:
            raise ValueError(str(exc)) from exc

    @field_validator("discount_rate", mode="before")
    @classmethod
    def coerce_discount_rate(cls, value: Any | None) -> float | None:
        if value is None:
            return None
        if isinstance(value, (int, float)):
            return float(value)
        text = _normalise_string(value)
        if not text:
            return None
        if text.endswith("%"):
            text = text[:-1]
        try:
            return float(text)
        except ValueError as exc:
            raise ValueError("Discount rate must be numeric") from exc

    @model_validator(mode="after")
    def validate_dates(self) -> "ScenarioImportRow":
        if self.start_date and self.end_date and self.start_date > self.end_date:
            raise ValueError("End date must be on or after start date")
        return self


class ImportRowErrorModel(BaseModel):
    row_number: int
    field: str | None = None
    message: str

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ImportPreviewRowIssueModel(BaseModel):
    message: str
    field: str | None = None

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ImportPreviewRowIssuesModel(BaseModel):
    row_number: int
    state: PreviewStateLiteral | None = None
    issues: list[ImportPreviewRowIssueModel] = Field(default_factory=list)

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ImportPreviewSummaryModel(BaseModel):
    total_rows: int
    accepted: int
    skipped: int
    errored: int

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ProjectImportPreviewRow(BaseModel):
    row_number: int
    data: ProjectImportRow
    state: PreviewStateLiteral
    issues: list[str] = Field(default_factory=list)
    context: dict[str, Any] | None = None

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ScenarioImportPreviewRow(BaseModel):
    row_number: int
    data: ScenarioImportRow
    state: PreviewStateLiteral
    issues: list[str] = Field(default_factory=list)
    context: dict[str, Any] | None = None

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ProjectImportPreviewResponse(BaseModel):
    rows: list[ProjectImportPreviewRow]
    summary: ImportPreviewSummaryModel
    row_issues: list[ImportPreviewRowIssuesModel] = Field(default_factory=list)
    parser_errors: list[ImportRowErrorModel] = Field(default_factory=list)
    stage_token: str | None = None

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ScenarioImportPreviewResponse(BaseModel):
    rows: list[ScenarioImportPreviewRow]
    summary: ImportPreviewSummaryModel
    row_issues: list[ImportPreviewRowIssuesModel] = Field(default_factory=list)
    parser_errors: list[ImportRowErrorModel] = Field(default_factory=list)
    stage_token: str | None = None

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ImportCommitSummaryModel(BaseModel):
    created: int
    updated: int

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ProjectImportCommitRow(BaseModel):
    row_number: int
    data: ProjectImportRow
    context: dict[str, Any]

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ScenarioImportCommitRow(BaseModel):
    row_number: int
    data: ScenarioImportRow
    context: dict[str, Any]

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ProjectImportCommitResponse(BaseModel):
    token: str
    rows: list[ProjectImportCommitRow]
    summary: ImportCommitSummaryModel

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ScenarioImportCommitResponse(BaseModel):
    token: str
    rows: list[ScenarioImportCommitRow]
    summary: ImportCommitSummaryModel

    model_config = ConfigDict(from_attributes=True, extra="forbid")


class ImportCommitRequest(BaseModel):
    token: str

    model_config = ConfigDict(extra="forbid")
