"""
Timeout Governor for Mid Platform.
[AC-IDMP-12] Timeout governance: per-tool <= 60s, end-to-end <= 180s.
[AC-MARH-08, AC-MARH-09] 超时口径统一：单工具 <= 60000ms，全链路 <= 180000ms。
"""

import asyncio
import logging
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, TypeVar

from app.models.mid.schemas import TimeoutProfile

logger = logging.getLogger(__name__)

DEFAULT_PER_TOOL_TIMEOUT_MS = 30000
DEFAULT_END_TO_END_TIMEOUT_MS = 120000
DEFAULT_LLM_TIMEOUT_MS = 60000

T = TypeVar("T")


@dataclass
class TimeoutResult:
    """Result of a timeout-governed operation."""
    success: bool
    result: Any = None
    error: str | None = None
    duration_ms: int = 0
    timed_out: bool = False


class TimeoutGovernor:
    """
    [AC-IDMP-12] Timeout governor for tool calls and end-to-end execution.
    [AC-MARH-08, AC-MARH-09] 超时口径统一。

    Constraints:
    - Per-tool timeout: <= 60000ms (60s)
    - LLM timeout: <= 120000ms (120s)
    - End-to-end timeout: <= 180000ms (180s)
    """

    def __init__(
        self,
        per_tool_timeout_ms: int = DEFAULT_PER_TOOL_TIMEOUT_MS,
        end_to_end_timeout_ms: int = DEFAULT_END_TO_END_TIMEOUT_MS,
        llm_timeout_ms: int = DEFAULT_LLM_TIMEOUT_MS,
    ):
        self._per_tool_timeout_ms = min(per_tool_timeout_ms, 60000)
        self._end_to_end_timeout_ms = min(end_to_end_timeout_ms, 180000)
        self._llm_timeout_ms = min(llm_timeout_ms, 120000)

    @property
    def per_tool_timeout_seconds(self) -> float:
        """Per-tool timeout in seconds."""
        return self._per_tool_timeout_ms / 1000.0

    @property
    def llm_timeout_seconds(self) -> float:
        """LLM call timeout in seconds."""
        return self._llm_timeout_ms / 1000.0

    @property
    def end_to_end_timeout_seconds(self) -> float:
        """End-to-end timeout in seconds."""
        return self._end_to_end_timeout_ms / 1000.0

    @property
    def profile(self) -> TimeoutProfile:
        """Get current timeout profile."""
        return TimeoutProfile(
            per_tool_timeout_ms=self._per_tool_timeout_ms,
            llm_timeout_ms=self._llm_timeout_ms,
            end_to_end_timeout_ms=self._end_to_end_timeout_ms,
        )

    async def execute_with_timeout(
        self,
        coro: Callable[[], T],
        timeout_ms: int | None = None,
        operation_name: str = "operation",
    ) -> TimeoutResult:
        """
        [AC-MARH-08, AC-MARH-09] Execute a coroutine with timeout.

        Args:
            coro: Coroutine to execute
            timeout_ms: Timeout in milliseconds (defaults to per-tool timeout)
            operation_name: Name for logging

        Returns:
            TimeoutResult with success status and result/error
        """
        import time

        timeout_ms = timeout_ms or self._per_tool_timeout_ms
        timeout_seconds = timeout_ms / 1000.0
        start_time = time.time()

        try:
            result = await asyncio.wait_for(coro(), timeout=timeout_seconds)
            duration_ms = int((time.time() - start_time) * 1000)

            logger.debug(
                f"[AC-MARH-08] {operation_name} completed in {duration_ms}ms"
            )

            return TimeoutResult(
                success=True,
                result=result,
                duration_ms=duration_ms,
            )

        except asyncio.TimeoutError:
            duration_ms = int((time.time() - start_time) * 1000)
            logger.warning(
                f"[AC-MARH-08] {operation_name} timed out after {duration_ms}ms "
                f"(limit: {timeout_ms}ms)"
            )
            return TimeoutResult(
                success=False,
                error=f"Timeout after {timeout_ms}ms",
                duration_ms=duration_ms,
                timed_out=True,
            )

        except Exception as e:
            duration_ms = int((time.time() - start_time) * 1000)
            logger.error(
                f"[AC-MARH-08] {operation_name} failed: {e}"
            )
            return TimeoutResult(
                success=False,
                error=str(e),
                duration_ms=duration_ms,
            )

    async def execute_tool(
        self,
        tool_name: str,
        tool_func: Callable[[], T],
    ) -> TimeoutResult:
        """
        [AC-MARH-08] Execute a tool call with per-tool timeout.
        """
        return await self.execute_with_timeout(
            coro=tool_func,
            timeout_ms=self._per_tool_timeout_ms,
            operation_name=f"tool:{tool_name}",
        )

    async def execute_e2e(
        self,
        coro: Callable[[], T],
        operation_name: str = "e2e",
    ) -> TimeoutResult:
        """
        [AC-MARH-09] Execute with end-to-end timeout.
        """
        return await self.execute_with_timeout(
            coro=coro,
            timeout_ms=self._end_to_end_timeout_ms,
            operation_name=operation_name,
        )
