"""Concierge preview planner for discovery recommendations."""

from __future__ import annotations

from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field

from g_gremlin.autopilot.discovery import DiscoveryReport, ScoutRecommendation
from g_gremlin.autopilot.modules import (
    MODULE_REGISTRY,
    ModuleStore,
    default_module_envelope,
    get_module_definition,
)
from g_gremlin.autopilot.scout_config_store import ScoutConfigStore
from g_gremlin.scout.engine import ScoutEngine
from g_gremlin.scout.loader import ScoutLoader
from g_gremlin.scout.models import ScoutDefinition

ActionType = Literal[
    "scout_config.upsert",
    "module_config.upsert",
    "scout_run.execute",
    "runtime_policy.reload",
]
PriorityType = Literal["high", "medium", "low"]

_PRIORITY_RANK: dict[str, int] = {"high": 3, "medium": 2, "low": 1}


class ConciergeActionDiff(BaseModel):
    model_config = ConfigDict(extra="ignore")

    before: dict[str, Any] = Field(default_factory=dict)
    after: dict[str, Any] = Field(default_factory=dict)


class ConciergeAction(BaseModel):
    model_config = ConfigDict(extra="ignore")

    action_id: str
    action_type: ActionType
    target: str
    summary: str
    priority: PriorityType
    risk_level: int
    diff: ConciergeActionDiff | None = None
    source_recommendation: str | None = None


class ConciergePlanStats(BaseModel):
    model_config = ConfigDict(extra="ignore")

    total_actions: int
    scout_config_actions: int
    module_config_actions: int
    scout_run_actions: int
    policy_reload_actions: int


class ConciergePlan(BaseModel):
    model_config = ConfigDict(extra="ignore")

    run_id: str
    actions: list[ConciergeAction] = Field(default_factory=list)
    warnings: list[str] = Field(default_factory=list)
    stats: ConciergePlanStats


class ConciergePlanner:
    def __init__(
        self,
        report: DiscoveryReport,
        scout_config_store: ScoutConfigStore,
        module_store: ModuleStore,
        scout_loader: ScoutLoader,
        scout_engine: ScoutEngine,
    ) -> None:
        self.report = report
        self.scout_config_store = scout_config_store
        self.module_store = module_store
        self.scout_loader = scout_loader
        self.scout_engine = scout_engine

    def plan(self, *, include_validation_scan: bool = True) -> ConciergePlan:
        actions: list[ConciergeAction] = []
        warnings: list[str] = []

        scout_actions, scout_warnings = self._plan_scout_config_actions()
        actions.extend(scout_actions)
        warnings.extend(scout_warnings)

        module_actions = self._plan_module_actions()
        actions.extend(module_actions)

        if include_validation_scan and any(
            action.action_type == "scout_config.upsert" for action in actions
        ):
            actions.append(
                ConciergeAction(
                    action_id="scout_run.execute:discovery",
                    action_type="scout_run.execute",
                    target="discovery",
                    summary="Run validation scan with updated scout config",
                    priority="medium",
                    risk_level=0,
                )
            )

        if any(
            action.action_type in {"scout_config.upsert", "module_config.upsert"}
            for action in actions
        ):
            actions.append(
                ConciergeAction(
                    action_id="runtime_policy.reload:policy",
                    action_type="runtime_policy.reload",
                    target="policy",
                    summary="Reload effective runtime policy",
                    priority="low",
                    risk_level=0,
                )
            )

        return ConciergePlan(
            run_id=self.report.run_id,
            actions=actions,
            warnings=warnings,
            stats=self.compute_stats(actions),
        )

    @staticmethod
    def compute_stats(actions: list[ConciergeAction]) -> ConciergePlanStats:
        return ConciergePlanStats(
            total_actions=len(actions),
            scout_config_actions=sum(1 for action in actions if action.action_type == "scout_config.upsert"),
            module_config_actions=sum(1 for action in actions if action.action_type == "module_config.upsert"),
            scout_run_actions=sum(1 for action in actions if action.action_type == "scout_run.execute"),
            policy_reload_actions=sum(1 for action in actions if action.action_type == "runtime_policy.reload"),
        )

    def _plan_scout_config_actions(self) -> tuple[list[ConciergeAction], list[str]]:
        merged_by_scout: dict[str, dict[str, Any]] = {}
        for recommendation in self.report.scout_recommendations:
            if recommendation.action not in {"configure", "enable"}:
                continue
            entry = merged_by_scout.get(recommendation.scout_name)
            if entry is None:
                entry = {
                    "suggested_params": {},
                    "priority": recommendation.priority,
                    "action": recommendation.action,
                    "summary_source": recommendation.rationale,
                    "source_recommendation": recommendation.scout_name,
                }
                merged_by_scout[recommendation.scout_name] = entry

            entry["suggested_params"].update(dict(recommendation.suggested_params or {}))
            if _PRIORITY_RANK.get(recommendation.priority, 0) > _PRIORITY_RANK.get(
                str(entry["priority"]), 0
            ):
                entry["priority"] = recommendation.priority
            if recommendation.action == "configure":
                entry["action"] = "configure"
            entry["summary_source"] = recommendation.rationale
            entry["source_recommendation"] = recommendation.scout_name

        if not merged_by_scout:
            return [], []

        warnings: list[str] = []
        actions: list[ConciergeAction] = []
        definitions = self.scout_loader.list_scouts()
        for scout_name, merged in merged_by_scout.items():
            definition = self.scout_loader.get_scout(scout_name)
            if definition is None:
                warnings.append(f"Scout definition not found: {scout_name}")
                continue

            merged_params = dict(merged["suggested_params"])
            warnings.extend(self.scout_config_store.validate_params(scout_name, merged_params, definitions))

            current_overrides = self.scout_config_store.load_scout(scout_name)
            before = self.scout_engine.resolve_params(definition, current_overrides)

            proposed_overrides = dict(current_overrides)
            proposed_overrides.update(merged_params)
            after = self.scout_engine.resolve_params(definition, proposed_overrides)

            if before == after:
                continue

            summary = self._scout_action_summary(
                definition=definition,
                recommendation_action=str(merged["action"]),
                rationale=str(merged["summary_source"]),
            )
            actions.append(
                ConciergeAction(
                    action_id=f"scout_config.upsert:{scout_name}",
                    action_type="scout_config.upsert",
                    target=scout_name,
                    summary=summary,
                    priority=self._coerce_priority(str(merged["priority"])),
                    risk_level=0,
                    diff=ConciergeActionDiff(before=before, after=after),
                    source_recommendation=str(merged["source_recommendation"]),
                )
            )

        return actions, warnings

    def _plan_module_actions(self) -> list[ConciergeAction]:
        by_action_id: dict[str, ConciergeAction] = {}
        for recommendation in self.report.scout_recommendations:
            if recommendation.action != "review":
                continue
            module_id = recommendation.scout_name
            if module_id not in MODULE_REGISTRY:
                continue

            definition = get_module_definition(module_id)
            envelope = self.module_store.load_module(module_id)
            if envelope is None:
                envelope = default_module_envelope(module_id)

            before = dict(envelope.config or {})
            after = dict(before)
            source_scout = self._extract_source_scout(recommendation)

            action = ConciergeAction(
                action_id=f"module_config.upsert:{module_id}",
                action_type="module_config.upsert",
                target=module_id,
                summary=(
                    f"Review {definition.display_name} configuration based on {source_scout} findings"
                ),
                priority=self._coerce_priority(recommendation.priority),
                risk_level=1,
                diff=ConciergeActionDiff(before=before, after=after),
                source_recommendation=recommendation.scout_name,
            )

            existing = by_action_id.get(action.action_id)
            if existing is None or self._is_higher_priority(action.priority, existing.priority):
                by_action_id[action.action_id] = action

        return list(by_action_id.values())

    @staticmethod
    def _extract_source_scout(recommendation: ScoutRecommendation) -> str:
        rationale = str(recommendation.rationale or "")
        marker = "Scout "
        found = rationale.find(marker)
        if found < 0:
            return recommendation.scout_name
        tail = rationale[found + len(marker) :]
        token = tail.split(" ", 1)[0].strip()
        return token or recommendation.scout_name

    @staticmethod
    def _is_higher_priority(candidate: str, current: str) -> bool:
        return _PRIORITY_RANK.get(candidate, 0) > _PRIORITY_RANK.get(current, 0)

    @staticmethod
    def _coerce_priority(value: str) -> PriorityType:
        lowered = (value or "medium").lower()
        if lowered in {"high", "medium", "low"}:
            return lowered  # type: ignore[return-value]
        return "medium"

    @staticmethod
    def _scout_action_summary(
        *,
        definition: ScoutDefinition,
        recommendation_action: str,
        rationale: str,
    ) -> str:
        if recommendation_action == "enable" and definition.enabled:
            return (
                f"{definition.display_name} is already enabled; "
                "apply recommended parameter tuning from discovery"
            )
        if rationale:
            return f"Update {definition.display_name} configuration: {rationale}"
        return f"Update {definition.display_name} configuration from discovery recommendations"


__all__ = [
    "ConciergeActionDiff",
    "ConciergeAction",
    "ConciergePlanStats",
    "ConciergePlan",
    "ConciergePlanner",
]
