"""Stable read projections for evaluation Campaigns."""

from typing import Any, Optional

from sqlmodel import Session

from agenteval.evaluation.campaign_scheduler import clock_offset, elapsed_seconds
from agenteval.evaluation.comparison import load_comparison_view
from agenteval.evaluation.report import (
    build_campaign_timeline,
    generate_campaign_report,
    summarize_campaign_progress,
)
from agenteval.exploration.summary import summarize_campaign_exploration
from agenteval.storage.db import iso_utc, utc_now
from agenteval.storage.repository import (
    CampaignAnalysisRepository,
    CampaignRepository,
    RunRepository,
    ScenarioRepository,
    TargetRepository,
)


class CampaignReadModel:
    """One interface for Campaign list, detail, report and export projections."""

    def __init__(self, session: Session):
        self._session = session
        self._campaigns = CampaignRepository(session)
        self._runs = RunRepository(session)
        self._scenarios = ScenarioRepository(session)
        self._analyses = CampaignAnalysisRepository(session)

    def list_items(self) -> list[dict[str, Any]]:
        campaigns = self._campaigns.list_all()
        campaign_ids = [campaign.id for campaign in campaigns if campaign.id]
        runs_by_campaign = self._runs.list_by_campaigns(campaign_ids)
        items = []
        for campaign in campaigns:
            data = campaign.model_dump()
            data["progress"] = summarize_campaign_progress(
                campaign,
                runs_by_campaign.get(campaign.id, []),
            )
            items.append(data)
        return items

    def detail(self, campaign_id: str) -> Optional[dict[str, Any]]:
        campaign = self._campaigns.get(campaign_id)
        if campaign is None:
            return None
        data = campaign.model_dump()
        runs = self._runs.list_by_campaign(campaign_id)
        current_offset = 0.0
        if campaign.started_at is not None:
            current_offset = min(
                clock_offset(
                    elapsed_seconds=elapsed_seconds(now=utc_now(), started_at=campaign.started_at),
                    time_scale=campaign.time_scale,
                ),
                float(campaign.window_seconds),
            )
        data["progress"] = {
            "current_offset_seconds": current_offset,
            "spawned_runs": len(runs),
            "completed_runs": sum(1 for run in runs if run.status.value == "completed"),
        }
        return data

    def report(self, campaign_id: str) -> Optional[dict[str, Any]]:
        campaign = self._campaigns.get(campaign_id)
        if campaign is None:
            return None
        report = self._core_report(campaign)
        exploration = summarize_campaign_exploration(self._session, campaign_id)
        if exploration is not None:
            report["exploration"] = exploration
        return report

    def timeline(self, campaign_id: str) -> Optional[dict[str, Any]]:
        campaign = self._campaigns.get(campaign_id)
        if campaign is None:
            return None
        entries = build_campaign_timeline(
            campaign,
            self._runs.list_by_campaign(campaign_id),
            scenario_names=self._scenarios.name_map(),
        )
        return {"entries": entries}

    def analysis(self, campaign_id: str) -> Optional[dict[str, Any]]:
        if self._campaigns.get(campaign_id) is None:
            return None
        row = self._analyses.get_by_campaign(campaign_id)
        if row is None:
            return {"status": "none"}
        return {
            "status": row.status,
            "result": row.get_result(),
            "error": row.error,
            "model_config_id": row.model_config_id,
            "triggered_by": row.triggered_by,
            "updated_at": iso_utc(row.updated_at),
        }

    def comparison(self, campaign_id: str) -> Optional[dict[str, Any]]:
        campaign = self._campaigns.get(campaign_id)
        return load_comparison_view(self._session, campaign) if campaign is not None else None

    def full_view(self, campaign_id: str) -> Optional[dict[str, Any]]:
        campaign = self._campaigns.get(campaign_id)
        if campaign is None:
            return None
        report = self._core_report(campaign)
        exploration = summarize_campaign_exploration(self._session, campaign_id)
        analysis = self.analysis(campaign_id)
        comparison = load_comparison_view(self._session, campaign)
        return {
            "report": report,
            "exploration": exploration,
            "analysis": analysis.get("result") if analysis and analysis.get("status") == "completed" else None,
            "comparison": comparison if comparison.get("status") != "none" else None,
        }

    def markdown_projection(self, campaign_id: str) -> Optional[dict[str, Any]]:
        campaign = self._campaigns.get(campaign_id)
        view = self.full_view(campaign_id)
        if campaign is None or view is None:
            return None

        comparison = None
        comparison_view = view["comparison"]
        if comparison_view and comparison_view.get("status") == "completed":
            row = comparison_view.get("comparison") or {}
            baseline = row.get("baseline") or {}
            comparison = {
                "result": row.get("result"),
                "baseline_name": baseline.get("name"),
                "baseline_completed_at": baseline.get("completed_at"),
                "model_name": row.get("model_name"),
                "updated_at": row.get("updated_at"),
                "metric_diff": comparison_view.get("metric_diff"),
            }

        target = TargetRepository(self._session).get(campaign.target_id)
        return {
            **view,
            "comparison": comparison,
            "target_name": target.name if target else None,
            "scenario_names": self._scenarios.name_map(),
        }

    def _core_report(self, campaign) -> dict[str, Any]:
        return generate_campaign_report(
            campaign,
            self._runs.list_by_campaign(campaign.id),
            scenario_names=self._scenarios.name_map(),
        )
