"""Tool approval request API interactions."""

from __future__ import annotations

import asyncio
import enum
from dataclasses import dataclass
from datetime import datetime
from typing import Optional, Iterable, TypedDict, Any, cast
from urllib.parse import urljoin

import requests

from ..logging import get_logger

logger = get_logger(__name__)
REQUEST_TIMEOUT_SECONDS = 10


class ToolApprovalStatus(str, enum.Enum):
    PENDING = "PENDING"
    APPROVED = "APPROVED"
    REJECTED = "REJECTED"
    CANCELED = "CANCELED"


@dataclass
class ToolApprovalRequest:
    """Response from the tool approval request API endpoint."""

    id: str
    tool_call_id: str
    session_id: str
    policy_id: str
    policy_version_id: str
    tool_name: str
    agent_id: str
    status: ToolApprovalStatus
    organization_id: str
    requested_at: datetime
    decided_at: Optional[datetime]
    decided_by_user_id: Optional[str]
    decision_notes: Optional[str]

    @classmethod
    def from_dict(cls, data: dict) -> "ToolApprovalRequest":
        """Parse the API response into a ToolApprovalRequest object."""
        return cls(
            id=data["id"],
            tool_call_id=data["toolCallId"],
            session_id=data["sessionId"],
            policy_id=data["policyId"],
            policy_version_id=data["policyVersionId"],
            tool_name=data["toolName"],
            agent_id=data["agentId"],
            status=ToolApprovalStatus(data["status"]),
            organization_id=data["organizationId"],
            requested_at=datetime.fromisoformat(
                data["requestedAt"].replace("Z", "+00:00")
            ),
            decided_at=(
                datetime.fromisoformat(data["decidedAt"].replace("Z", "+00:00"))
                if data.get("decidedAt")
                else None
            ),
            decided_by_user_id=data.get("decidedByUserId"),
            decision_notes=data.get("decisionNotes"),
        )


class ToolApprovalRequestInput(TypedDict):
    tool_call_id: str
    policy_id: str
    policy_version_id: str
    tool_name: str


async def _post_json(
    endpoint: str, payload: Any, headers: dict[str, str]
) -> requests.Response:
    return await asyncio.to_thread(
        requests.post,
        endpoint,
        json=payload,
        headers=headers,
        timeout=REQUEST_TIMEOUT_SECONDS,
    )


async def get_or_create_many_tool_approval_requests(
    *,
    tool_approval_request_inputs: Iterable[ToolApprovalRequestInput],
    session_id: str,
    api_base_url: str,
    access_token: str,
) -> dict[str, ToolApprovalRequest | None]:
    """Batch-create (or fetch existing) tool approval requests.

    Calls `POST /api/tool_approval_requests/get_or_create_many` and returns a lookup
    keyed by `tool_call_id`.

    Partial/total failure behavior:
    - If the HTTP request fails or the response can't be parsed, returns a mapping
      where all requested tool_call_ids map to None.
    - If the API returns a subset of the requested tool_call_ids, missing ids map to None.
    """

    inputs_list = list(tool_approval_request_inputs)

    # Return early for empty input (and avoid a network call).
    if not inputs_list:
        return {}

    requested_tool_call_ids = [item["tool_call_id"] for item in inputs_list]

    inputs_payload: list[dict[str, str]] = [
        {
            "toolCallId": item["tool_call_id"],
            "policyId": item["policy_id"],
            "policyVersionId": item["policy_version_id"],
            "toolName": item["tool_name"],
        }
        for item in inputs_list
    ]

    endpoint = urljoin(api_base_url, "/api/tool_approval_requests/get_or_create_many")

    payload = {
        "inputs": inputs_payload,
        # Backend schema requires these. We don't have them in this function signature yet,
        # so we send placeholders until the SDK wiring provides real values.
        "agentId": "unknown",
        "organizationId": "unknown",
        "sessionId": session_id,
        "requestedAt": None,
    }

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }

    # Default: pessimistically assume None for all tool call ids.
    approval_requests_lookup: dict[str, ToolApprovalRequest | None] = {
        tool_call_id: None for tool_call_id in requested_tool_call_ids
    }

    try:
        logger.debug(
            "Batch creating tool approval requests for %d tool calls",
            len(requested_tool_call_ids),
        )
        response = await _post_json(endpoint, payload, headers)
        response.raise_for_status()

        response_json = response.json()
        if not isinstance(response_json, list):
            raise ValueError("Expected list response from get_or_create_many endpoint")

        parsed: list[ToolApprovalRequest] = []
        for idx, item in enumerate(response_json):
            try:
                parsed.append(ToolApprovalRequest.from_dict(cast(dict, item)))
            except (KeyError, ValueError, TypeError) as e:
                logger.error(
                    "Failed to parse tool approval request at index %d from batch response: %s",
                    idx,
                    e,
                )

        for req in parsed:
            approval_requests_lookup[req.tool_call_id] = req

        logger.info(
            "Batch tool approval requests: received %d/%d",
            len(parsed),
            len(requested_tool_call_ids),
        )
        return approval_requests_lookup

    except requests.exceptions.HTTPError as e:
        status = getattr(e.response, "status_code", "unknown")
        text = getattr(e.response, "text", "")
        logger.error(
            f"Failed to batch create tool approval requests: HTTP {status} - {text}"
        )
        return approval_requests_lookup
    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to batch create tool approval requests: {e}")
        return approval_requests_lookup
    except (ValueError, TypeError) as e:
        logger.error(f"Failed to parse batch tool approval requests response: {e}")
        return approval_requests_lookup


async def get_or_create_tool_approval_request(
    *,
    tool_call_id: str,
    tool_name: str,
    policy_id: str,
    policy_version_id: str,
    session_id: str,
    api_base_url: str,
    access_token: str,
) -> Optional[ToolApprovalRequest]:
    """Create a tool approval request via the Frisk API.

    Args:
        tool_call_id: The tool call identifier that needs approval
        tool_name: The tool name associated with the tool call
        policy_id: The policy identifier that triggered escalation
        policy_version_id: The policy version identifier that triggered escalation
        session_id: The current session identifier
        api_base_url: Base URL for the Frisk API
        access_token: Access token for authentication

    Returns:
        ToolApprovalRequest object if successful, None otherwise.
    """
    endpoint = urljoin(api_base_url, "/api/tool_approval_requests/get_or_create")

    payload = {
        "toolCallId": tool_call_id,
        "policyId": policy_id,
        "policyVersionId": policy_version_id,
        "sessionId": session_id,
        "toolName": tool_name,
    }

    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }

    try:
        logger.debug("Creating tool approval request for tool call %s", tool_call_id)
        response = await _post_json(endpoint, payload, headers)
        response.raise_for_status()

        result = ToolApprovalRequest.from_dict(response.json())
        logger.info("Created tool approval request: %s", result.id)
        return result

    except requests.exceptions.HTTPError as e:
        status = getattr(getattr(e, "response", None), "status_code", "unknown")
        text = getattr(getattr(e, "response", None), "text", "")
        logger.error(f"Failed to create tool approval request: HTTP {status} - {text}")
        return None
    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to create tool approval request: {e}")
        return None
    except (KeyError, ValueError) as e:
        logger.error(f"Failed to parse tool approval request response: {e}")
        return None


@dataclass
class ToolApprovalRequestStatusUpdateResult:
    updated: bool
    result: ToolApprovalRequest | None


async def cancel_many_tool_approval_requests(
    *,
    approval_request_ids: list[str],
    api_base_url: str,
    access_token: str,
) -> dict[str, ToolApprovalRequestStatusUpdateResult | None]:
    """Cancel tool approval requests for a given list of approval request IDs."""

    endpoint = urljoin(api_base_url, "/api/tool_approval_requests/cancel_many")
    payload = {"ids": approval_request_ids}
    headers = {
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
    }

    update_results_by_request_id: dict[
        str, ToolApprovalRequestStatusUpdateResult | None
    ] = {approval_request_id: None for approval_request_id in approval_request_ids}

    try:
        logger.debug("Cancelling %d tool approval requests", len(approval_request_ids))
        response = await _post_json(endpoint, payload, headers)
        response.raise_for_status()

        response_json = response.json()
        if not isinstance(response_json, list):
            raise ValueError("Expected list response from cancel_many endpoint")

        for idx, item in enumerate(response_json):
            try:
                if not isinstance(item, dict):
                    raise TypeError("Expected dict items in cancel_many response")

                item = cast(dict, item)
                updated = bool(item.get("updated"))
                updated_approval_request_json: dict | None = item.get("result")
                updated_approval_request = (
                    ToolApprovalRequest.from_dict(updated_approval_request_json)
                    if isinstance(updated_approval_request_json, dict)
                    else None
                )
                parsed_update_result = ToolApprovalRequestStatusUpdateResult(
                    updated=updated,
                    result=updated_approval_request,
                )

                if parsed_update_result.result is not None:
                    update_results_by_request_id[parsed_update_result.result.id] = (
                        parsed_update_result
                    )
            except Exception as e:
                logger.error(
                    "Failed to parse tool approval request cancellation response at index %d: %s",
                    idx,
                    e,
                )

        logger.info("Cancelled %d tool approval requests", len(approval_request_ids))
        return update_results_by_request_id

    except requests.exceptions.HTTPError as e:
        status = getattr(getattr(e, "response", None), "status_code", "unknown")
        text = getattr(getattr(e, "response", None), "text", "")
        logger.error(f"Failed to cancel tool approval requests: HTTP {status} - {text}")
        return update_results_by_request_id
    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to cancel tool approval requests: {e}")
        return update_results_by_request_id
    except (KeyError, ValueError, TypeError) as e:
        logger.error(f"Failed to parse tool approval request response: {e}")
        return update_results_by_request_id
