"""CQRS handlers and projection for the dashboard setup checklist."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

import structlog

from proxym.domain.commands.setup_commands import ResetSetupChecklistCommand, ToggleSetupStepCommand
from proxym.domain.events import EventStore, SetupChecklistReset, SetupStepToggled
from proxym.domain.queries.setup_queries import GetSetupChecklistQuery

logger = structlog.get_logger()

SETUP_CHECKLIST_AGGREGATE_ID = "setup-checklist"
_RELEVANT_EVENT_TYPES = {"SetupStepToggled", "SetupChecklistReset"}

_setup_projection: "SetupChecklistProjection | None" = None


@dataclass
class SetupChecklistSnapshot:
    """Current checklist state derived from the event log."""

    aggregate_id: str = SETUP_CHECKLIST_AGGREGATE_ID
    completed_steps: list[str] = field(default_factory=list)
    completed_map: dict[str, bool] = field(default_factory=dict)
    completed_count: int = 0
    updated_at: float | None = None
    revision: int = 0
    event_count: int = 0


class SetupChecklistProjection:
    """Event-sourced projection for dashboard setup progress."""

    def __init__(self, event_store: EventStore) -> None:
        self._event_store = event_store
        self._completed_map: dict[str, bool] = {}
        self._updated_at: float | None = None
        self._revision = 0
        self._seen_event_ids: set[str] = set()
        self._load_from_store()
        self._event_store.subscribe(self._on_event)

    @property
    def event_store(self) -> EventStore:
        return self._event_store

    def _load_from_store(self) -> None:
        for event in self._event_store.events_for(SETUP_CHECKLIST_AGGREGATE_ID):
            self._apply_event(event)

    def _on_event(self, event) -> None:
        if getattr(event, "aggregate_id", None) != SETUP_CHECKLIST_AGGREGATE_ID:
            return
        if getattr(event, "event_type", None) not in _RELEVANT_EVENT_TYPES:
            return
        if getattr(event, "event_id", None) in self._seen_event_ids:
            return
        self._apply_event(event)

    def _apply_event(self, event) -> None:
        event_id = getattr(event, "event_id", None)
        if event_id:
            self._seen_event_ids.add(event_id)

        self._revision += 1
        self._updated_at = getattr(event, "timestamp", None)

        event_type = getattr(event, "event_type", "")
        if event_type == "SetupChecklistReset":
            self._completed_map.clear()
            return

        if event_type == "SetupStepToggled":
            payload = getattr(event, "data", {}) or {}
            step_id = str(payload.get("step_id") or "").strip()
            if not step_id:
                logger.warning("setup_step_event_missing_step_id", event_id=event_id)
                return
            self._completed_map[step_id] = bool(payload.get("completed", False))

    def is_completed(self, step_id: str) -> bool:
        return bool(self._completed_map.get(step_id, False))

    def snapshot(self) -> dict[str, Any]:
        completed_steps = sorted(step_id for step_id, completed in self._completed_map.items() if completed)
        return {
            "aggregate_id": SETUP_CHECKLIST_AGGREGATE_ID,
            "completed_steps": completed_steps,
            "completed_map": dict(self._completed_map),
            "completed_count": len(completed_steps),
            "updated_at": self._updated_at,
            "revision": self._revision,
            "event_count": len(self._seen_event_ids),
        }


class SetupCommandHandler:
    """Handle setup checklist commands and emit domain events."""

    def __init__(self, projection: SetupChecklistProjection) -> None:
        self._projection = projection

    def handle(self, command: Any) -> dict[str, Any]:
        if isinstance(command, ToggleSetupStepCommand):
            return self._handle_toggle(command)
        if isinstance(command, ResetSetupChecklistCommand):
            return self._handle_reset(command)
        raise ValueError(f"Unknown command: {type(command).__name__}")

    def _handle_toggle(self, command: ToggleSetupStepCommand) -> dict[str, Any]:
        step_id = command.step_id.strip()
        if not step_id:
            raise ValueError("step_id is required")

        current = self._projection.is_completed(step_id)
        completed = (not current) if command.completed is None else bool(command.completed)

        if completed == current:
            return self._projection.snapshot()

        self._projection.event_store.append(
            SetupStepToggled(
                aggregate_id=SETUP_CHECKLIST_AGGREGATE_ID,
                data={"step_id": step_id, "completed": completed},
                actor=command.actor,
            )
        )
        return self._projection.snapshot()

    def _handle_reset(self, command: ResetSetupChecklistCommand) -> dict[str, Any]:
        current = self._projection.snapshot()
        if not current["completed_steps"]:
            return current

        self._projection.event_store.append(
            SetupChecklistReset(
                aggregate_id=SETUP_CHECKLIST_AGGREGATE_ID,
                data={"cleared": True},
                actor=command.actor,
            )
        )
        return self._projection.snapshot()


class SetupQueryHandler:
    """Return the current setup checklist projection."""

    def __init__(self, projection: SetupChecklistProjection) -> None:
        self._projection = projection

    def handle(self, query: Any) -> dict[str, Any]:
        if not isinstance(query, GetSetupChecklistQuery):
            raise ValueError(f"Unknown query: {type(query).__name__}")
        return self._projection.snapshot()


def get_setup_projection(event_store: EventStore) -> SetupChecklistProjection:
    """Get or create the singleton setup projection for the active event store."""
    global _setup_projection
    if _setup_projection is None or _setup_projection.event_store is not event_store:
        _setup_projection = SetupChecklistProjection(event_store)
    return _setup_projection


def reset_setup_projection() -> None:
    """Reset the singleton projection (used by tests)."""
    global _setup_projection
    _setup_projection = None
