from __future__ import annotations

from dataclasses import dataclass
from datetime import date, datetime
from typing import Iterable

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


def _normalise_lower_strings(values: Iterable[str]) -> tuple[str, ...]:
    unique: set[str] = set()
    for value in values:
        if not value:
            continue
        trimmed = value.strip().lower()
        if not trimmed:
            continue
        unique.add(trimmed)
    return tuple(sorted(unique))


def _normalise_upper_strings(values: Iterable[str | None]) -> tuple[str, ...]:
    unique: set[str] = set()
    for value in values:
        if value is None:
            continue
        candidate = value if isinstance(value, str) else str(value)
        candidate = candidate.strip()
        if not candidate:
            continue
        try:
            normalised = normalise_currency(candidate)
        except CurrencyValidationError as exc:
            raise ValueError(str(exc)) from exc
        if normalised is None:
            continue
        unique.add(normalised)
    return tuple(sorted(unique))


@dataclass(slots=True, frozen=True)
class ProjectExportFilters:
    """Filter parameters for project export queries."""

    ids: tuple[int, ...] = ()
    names: tuple[str, ...] = ()
    name_contains: str | None = None
    locations: tuple[str, ...] = ()
    operation_types: tuple[MiningOperationType, ...] = ()
    created_from: datetime | None = None
    created_to: datetime | None = None
    updated_from: datetime | None = None
    updated_to: datetime | None = None

    def normalised_ids(self) -> tuple[int, ...]:
        unique = {identifier for identifier in self.ids if identifier > 0}
        return tuple(sorted(unique))

    def normalised_names(self) -> tuple[str, ...]:
        return _normalise_lower_strings(self.names)

    def normalised_locations(self) -> tuple[str, ...]:
        return _normalise_lower_strings(self.locations)

    def name_search_pattern(self) -> str | None:
        if not self.name_contains:
            return None
        pattern = self.name_contains.strip()
        if not pattern:
            return None
        return f"%{pattern}%"


@dataclass(slots=True, frozen=True)
class ScenarioExportFilters:
    """Filter parameters for scenario export queries."""

    ids: tuple[int, ...] = ()
    project_ids: tuple[int, ...] = ()
    project_names: tuple[str, ...] = ()
    name_contains: str | None = None
    statuses: tuple[ScenarioStatus, ...] = ()
    start_date_from: date | None = None
    start_date_to: date | None = None
    end_date_from: date | None = None
    end_date_to: date | None = None
    created_from: datetime | None = None
    created_to: datetime | None = None
    updated_from: datetime | None = None
    updated_to: datetime | None = None
    currencies: tuple[str, ...] = ()
    primary_resources: tuple[ResourceType, ...] = ()

    def normalised_ids(self) -> tuple[int, ...]:
        unique = {identifier for identifier in self.ids if identifier > 0}
        return tuple(sorted(unique))

    def normalised_project_ids(self) -> tuple[int, ...]:
        unique = {identifier for identifier in self.project_ids if identifier > 0}
        return tuple(sorted(unique))

    def normalised_project_names(self) -> tuple[str, ...]:
        return _normalise_lower_strings(self.project_names)

    def name_search_pattern(self) -> str | None:
        if not self.name_contains:
            return None
        pattern = self.name_contains.strip()
        if not pattern:
            return None
        return f"%{pattern}%"

    def normalised_currencies(self) -> tuple[str, ...]:
        return _normalise_upper_strings(self.currencies)


__all__ = (
    "ProjectExportFilters",
    "ScenarioExportFilters",
)
