"""
Infospace runtime state.

Computed from the current entities, evaluations, and metrics on disk.
Provides the data behind ``markitect infospace status`` and
``markitect infospace viability``.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

from markitect.infospace.config import InfospaceConfig, ViabilityThreshold
from markitect.infospace.models import EntityMeta
from markitect.infospace.evaluation import EvaluationSnapshot


@dataclass
class ViabilityResult:
    """Result of checking a single viability threshold."""

    metric: str
    value: float
    threshold: ViabilityThreshold
    passed: bool

    def to_dict(self) -> Dict[str, Any]:
        d: Dict[str, Any] = {
            "metric": self.metric,
            "value": self.value,
            "passed": self.passed,
        }
        if self.threshold.min is not None:
            d["min"] = self.threshold.min
        if self.threshold.max is not None:
            d["max"] = self.threshold.max
        return d


@dataclass
class InfospaceState:
    """Current runtime state of an infospace.

    Aggregates entity metadata, evaluation results, and viability
    checks into a single queryable object.
    """

    config: InfospaceConfig
    entities: List[EntityMeta] = field(default_factory=list)
    latest_snapshot: Optional[EvaluationSnapshot] = None
    viability_results: List[ViabilityResult] = field(default_factory=list)
    computed_at: datetime = field(default_factory=datetime.utcnow)

    @property
    def entity_count(self) -> int:
        return len(self.entities)

    @property
    def topic_name(self) -> str:
        return self.config.topic.name

    @property
    def is_viable(self) -> bool:
        """``True`` if all viability thresholds are met."""
        if not self.viability_results:
            return False
        return all(r.passed for r in self.viability_results)

    @property
    def viability_pass_count(self) -> int:
        return sum(1 for r in self.viability_results if r.passed)

    @property
    def viability_total_count(self) -> int:
        return len(self.viability_results)

    @property
    def domains(self) -> List[str]:
        """Distinct domain values across all entities."""
        return sorted({e.domain for e in self.entities if e.domain})

    @property
    def has_evaluations(self) -> bool:
        return self.latest_snapshot is not None

    def check_viability(self, metrics: Dict[str, float]) -> List[ViabilityResult]:
        """Check *metrics* against the configured viability thresholds.

        Updates :attr:`viability_results` and returns the results.
        """
        results: List[ViabilityResult] = []
        for name, threshold in self.config.viability.items():
            value = metrics.get(name, 0.0)
            results.append(ViabilityResult(
                metric=name,
                value=value,
                threshold=threshold,
                passed=threshold.check(value),
            ))
        self.viability_results = results
        return results

    def summary(self) -> Dict[str, Any]:
        """Return a summary dict suitable for display or serialisation."""
        d: Dict[str, Any] = {
            "topic": self.topic_name,
            "entity_count": self.entity_count,
            "domains": self.domains,
            "has_evaluations": self.has_evaluations,
        }
        if self.viability_results:
            d["viable"] = self.is_viable
            d["viability_pass"] = self.viability_pass_count
            d["viability_total"] = self.viability_total_count
        if self.latest_snapshot:
            d["last_evaluated"] = self.latest_snapshot.created_at.isoformat()
        return d


def build_state(
    config: InfospaceConfig,
    entities: Optional[List[EntityMeta]] = None,
    snapshot: Optional[EvaluationSnapshot] = None,
    metrics: Optional[Dict[str, float]] = None,
) -> InfospaceState:
    """Build an :class:`InfospaceState` from available data.

    This is a convenience function that assembles the state object
    and optionally runs viability checks if *metrics* are provided.
    """
    if not isinstance(config, InfospaceConfig):
        raise TypeError(
            f"build_state(config=...) expects an InfospaceConfig instance, "
            f"got {type(config).__name__}. If you have a path, load the "
            f"config first with load_infospace_config(path)."
        )
    state = InfospaceState(
        config=config,
        entities=entities or [],
        latest_snapshot=snapshot,
    )
    if metrics is not None:
        state.check_viability(metrics)
    return state
