"""
agent/confirmation.py — Confirmation flow for side-effect tool calls.

When a tool has requires_confirmation=True, the agent loop calls
ConfirmationManager.request(). This suspends the tool call and returns
control to the web layer, which shows the user a Yes/No prompt.

The web route calls ConfirmationManager.respond() when the user decides.
The suspended coroutine resumes with the result.

Pending confirmations expire after TIMEOUT_SECONDS.
"""
from __future__ import annotations

import asyncio
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone

logger = logging.getLogger(__name__)

TIMEOUT_SECONDS = 300  # 5 minutes


@dataclass
class PendingConfirmation:
    session_id: str
    tool_name: str
    arguments: dict
    description: str           # Human-readable summary shown to user
    created_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
    _event: asyncio.Event = field(default_factory=asyncio.Event, repr=False)
    _approved: bool = False

    def to_dict(self) -> dict:
        return {
            "session_id": self.session_id,
            "tool_name": self.tool_name,
            "arguments": self.arguments,
            "description": self.description,
            "created_at": self.created_at.isoformat(),
        }


class ConfirmationManager:
    """
    Singleton-style manager. One instance shared across the app.
    Thread-safe for asyncio (single event loop).
    """

    def __init__(self) -> None:
        self._pending: dict[str, PendingConfirmation] = {}

    async def request(
        self,
        session_id: str,
        tool_name: str,
        arguments: dict,
        description: str,
    ) -> bool:
        """
        Called by the agent loop when a tool requires confirmation.
        Suspends until the user responds (Yes/No) or the timeout expires.

        Returns True if approved, False if denied or timed out.
        """
        if session_id in self._pending:
            # Previous confirmation timed out and wasn't cleaned up
            logger.warning(f"Overwriting stale pending confirmation for session {session_id}")

        confirmation = PendingConfirmation(
            session_id=session_id,
            tool_name=tool_name,
            arguments=arguments,
            description=description,
        )
        self._pending[session_id] = confirmation

        try:
            await asyncio.wait_for(confirmation._event.wait(), timeout=TIMEOUT_SECONDS)
            approved = confirmation._approved
        except asyncio.TimeoutError:
            logger.info(f"Confirmation timed out for session {session_id} / tool {tool_name}")
            approved = False
        finally:
            self._pending.pop(session_id, None)

        action = "approved" if approved else "denied/timed out"
        logger.info(f"Confirmation {action}: session={session_id} tool={tool_name}")
        return approved

    def respond(self, session_id: str, approved: bool) -> bool:
        """
        Called by the web route (/api/confirm) when the user clicks Yes or No.
        Returns False if no pending confirmation exists for this session.
        """
        confirmation = self._pending.get(session_id)
        if confirmation is None:
            logger.warning(f"No pending confirmation for session {session_id}")
            return False

        confirmation._approved = approved
        confirmation._event.set()
        return True

    def get_pending(self, session_id: str) -> PendingConfirmation | None:
        return self._pending.get(session_id)

    def list_pending(self) -> list[dict]:
        return [c.to_dict() for c in self._pending.values()]


# Module-level singleton
confirmation_manager = ConfirmationManager()
