from __future__ import annotations

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

from models import Scenario, ScenarioStatus
from services.exceptions import ScenarioValidationError

ALLOWED_STATUSES: frozenset[ScenarioStatus] = frozenset(
    {ScenarioStatus.DRAFT, ScenarioStatus.ACTIVE}
)


@dataclass(frozen=True)
class _ValidationContext:
    scenarios: Sequence[Scenario]

    @property
    def scenario_ids(self) -> list[int]:
        return [scenario.id for scenario in self.scenarios if scenario.id is not None]


class ScenarioComparisonValidator:
    """Validates scenarios prior to comparison workflows."""

    def validate(self, scenarios: Sequence[Scenario] | Iterable[Scenario]) -> None:
        scenario_list = list(scenarios)
        if len(scenario_list) < 2:
            # Nothing to validate when fewer than two scenarios are provided.
            return

        context = _ValidationContext(scenario_list)

        self._ensure_same_project(context)
        self._ensure_allowed_status(context)
        self._ensure_shared_currency(context)
        self._ensure_timeline_overlap(context)
        self._ensure_shared_primary_resource(context)

    def _ensure_same_project(self, context: _ValidationContext) -> None:
        project_ids = {scenario.project_id for scenario in context.scenarios}
        if len(project_ids) > 1:
            raise ScenarioValidationError(
                code="SCENARIO_PROJECT_MISMATCH",
                message="Selected scenarios do not belong to the same project.",
                scenario_ids=context.scenario_ids,
            )

    def _ensure_allowed_status(self, context: _ValidationContext) -> None:
        disallowed = [
            scenario
            for scenario in context.scenarios
            if scenario.status not in ALLOWED_STATUSES
        ]
        if disallowed:
            raise ScenarioValidationError(
                code="SCENARIO_STATUS_INVALID",
                message="Archived scenarios cannot be compared.",
                scenario_ids=[
                    scenario.id for scenario in disallowed if scenario.id is not None],
            )

    def _ensure_shared_currency(self, context: _ValidationContext) -> None:
        currencies = {
            scenario.currency
            for scenario in context.scenarios
            if scenario.currency is not None
        }
        if len(currencies) > 1:
            raise ScenarioValidationError(
                code="SCENARIO_CURRENCY_MISMATCH",
                message="Scenarios use different currencies and cannot be compared.",
                scenario_ids=context.scenario_ids,
            )

    def _ensure_timeline_overlap(self, context: _ValidationContext) -> None:
        ranges = [
            (scenario.start_date, scenario.end_date)
            for scenario in context.scenarios
            if scenario.start_date and scenario.end_date
        ]
        if len(ranges) < 2:
            return

        latest_start: date = max(start for start, _ in ranges)
        earliest_end: date = min(end for _, end in ranges)
        if latest_start > earliest_end:
            raise ScenarioValidationError(
                code="SCENARIO_TIMELINE_DISJOINT",
                message="Scenario timelines do not overlap; adjust the comparison window.",
                scenario_ids=context.scenario_ids,
            )

    def _ensure_shared_primary_resource(self, context: _ValidationContext) -> None:
        resources = {
            scenario.primary_resource
            for scenario in context.scenarios
            if scenario.primary_resource is not None
        }
        if len(resources) > 1:
            raise ScenarioValidationError(
                code="SCENARIO_RESOURCE_MISMATCH",
                message="Scenarios target different primary resources and cannot be compared.",
                scenario_ids=context.scenario_ids,
            )
