# src/nthlayer_respond/agents/remediation.py
"""RemediationAgent — safe action execution with approval ratchet."""
from __future__ import annotations

import json
import structlog

from nthlayer_respond.agents.base import AgentBase
from nthlayer_respond.safe_actions.registry import SafeActionRegistry
from nthlayer_respond.types import (
    AgentRole,
    IncidentContext,
    RemediationResult,
)

logger = structlog.get_logger(__name__)


class RemediationAgent(AgentBase):
    """Suggest and execute pre-approved safe actions.

    Judgment SLO: 80% fix success rate.

    Safety properties:
    - Only actions in the closed SafeActionRegistry may be proposed.
    - Approval ratchet: registry.requires_approval=True can never be downgraded by
      the model.  The model may always escalate (say True when registry says False).
    - Novel actions (not in registry) are rejected: proposed_action set to None and
      requires_human_approval forced to True.
    """

    role = AgentRole.REMEDIATION
    default_timeout = 30

    def __init__(
        self,
        *args,
        safe_action_registry: SafeActionRegistry,
        **kwargs,
    ) -> None:
        super().__init__(*args, **kwargs)
        self._registry = safe_action_registry

    # ------------------------------------------------------------------ #
    # Judgment interface                                                   #
    # ------------------------------------------------------------------ #

    def build_prompt(self, context: IncidentContext) -> tuple[str, str]:
        actions = self._registry.list_actions()
        action_names = [a["name"] for a in actions]
        action_descriptions = "; ".join(
            f"{a['name']}: {a['description']}" for a in actions
        )

        system = (
            "You are a remediation agent. "
            "Recommend fixes from the safe action registry ONLY. "
            "You MUST NOT recommend novel actions. "
            "Assess blast radius. "
            "Judgment SLO: 80% fix success rate. "
            f"Available safe actions: {action_names}. "
            f"Action details: {action_descriptions}. "
            "Respond with ONLY valid JSON."
        )

        parts: list[str] = []

        # Investigation context
        if context.investigation is not None:
            inv = context.investigation
            parts.append("Investigation findings:")
            if inv.root_cause is not None:
                parts.append(f"  root_cause={inv.root_cause!r}")
                parts.append(f"  root_cause_confidence={inv.root_cause_confidence}")
            if inv.hypotheses:
                parts.append("  hypotheses:")
                for h in inv.hypotheses:
                    parts.append(
                        f"    - {h.description!r} (confidence={h.confidence}, "
                        f"change_candidate={h.change_candidate!r})"
                    )

        # Triage context
        if context.triage is not None:
            t = context.triage
            parts.append("Triage results:")
            parts.append(f"  severity={t.severity}")
            parts.append(f"  blast_radius={t.blast_radius}")
            parts.append(f"  affected_slos={t.affected_slos}")

        # Topology
        parts.append(f"\nTopology: {json.dumps(context.topology)}")

        user = "\n".join(parts)
        return system, user

    def parse_response(
        self, response: str, context: IncidentContext
    ) -> RemediationResult:
        data = self._parse_json(response)

        proposed_action: str | None = data.get("proposed_action")
        target: str | None = data.get("target")
        risk_assessment: str = data.get("risk_assessment", "")
        requires_human_approval: bool = bool(data.get("requires_human_approval", True))
        autonomy_reduction: dict = data.get("autonomy_reduction") or {}
        reasoning: str = data.get("reasoning", "")

        # Critical validation: reject hallucinated actions
        if proposed_action is not None:
            try:
                registry_action = self._registry.get(proposed_action)
            except KeyError:
                logger.warning(
                    "RemediationAgent: model proposed unknown action %r — rejecting "
                    "(hallucinated action name). Forcing requires_human_approval=True.",
                    proposed_action,
                )
                proposed_action = None
                requires_human_approval = True
                registry_action = None
            else:
                # Approval ratchet: registry can only escalate, never downgrade.
                # If registry says approval required, model cannot override it to False.
                if registry_action.requires_approval and not requires_human_approval:
                    requires_human_approval = True
        else:
            registry_action = None

        result = RemediationResult(
            proposed_action=proposed_action,
            target=target,
            risk_assessment=risk_assessment,
            requires_human_approval=requires_human_approval,
            reasoning=reasoning,
        )

        # Stash autonomy_reduction on the result for use in _post_execute
        result.autonomy_reduction = autonomy_reduction

        return result

    def _apply_result(
        self, context: IncidentContext, result: RemediationResult
    ) -> IncidentContext:
        context.remediation = result
        return context

    # ------------------------------------------------------------------ #
    # Post-execute: safe action + autonomy reduction                      #
    # ------------------------------------------------------------------ #

    async def _post_execute(
        self, context: IncidentContext, result: RemediationResult
    ) -> IncidentContext:
        """Execute safe action (if approved) then handle autonomy reduction.

        Strict ordering:
        1. Execute safe action (if not requires_human_approval and action is set)
        2. Autonomy reduction (if recommended)
        """
        # Step 1: Execute safe action
        if not result.requires_human_approval and result.proposed_action is not None:
            try:
                exec_result = await self._registry.execute(
                    result.proposed_action, result.target, context
                )
                result.executed = True
                result.execution_result = exec_result.get("detail")
            except Exception as exc:  # noqa: BLE001
                result.executed = False
                result.execution_result = str(exc)

        # Step 2: Autonomy reduction (if recommended by model)
        autonomy_reduction: dict = result.autonomy_reduction or {}
        if autonomy_reduction.get("recommended"):
            target_agent: str = autonomy_reduction.get("target_agent", "")
            arbiter_url: str = self._config.get("arbiter_url", "")
            reason: str = autonomy_reduction.get("reason", "")

            try:
                gov_response = await self._request_autonomy_reduction(
                    target_agent, arbiter_url, reason
                )
                result.autonomy_reduced = True
                result.autonomy_target = target_agent
                result.previous_autonomy_level = gov_response.get("previous_level")
                result.new_autonomy_level = gov_response.get("new_level")
            except Exception as exc:  # noqa: BLE001
                logger.warning(
                    "RemediationAgent: autonomy reduction request failed: %s", exc
                )

        return context
