"""Vorim SDK — Python client.

Thin HTTP client wrapping the Vorim AI REST API. Mirrors the TypeScript
SDK's interface so behaviour is identical across languages.

Usage::

    from vorim import Vorim

    vorim = Vorim(api_key="agid_sk_live_...")

    # Register an agent
    result = vorim.register(
        name="invoice-processor",
        capabilities=["read_documents", "extract_data"],
        scopes=["agent:read", "agent:execute"],
    )

    # Check permissions (<5ms via Redis)
    check = vorim.check(result.agent.agent_id, "agent:execute")

    # Emit audit event
    vorim.emit(agent_id=result.agent.agent_id, event_type="tool_call",
               action="process_invoice", result="success")
"""

from __future__ import annotations

from dataclasses import asdict
from typing import Any

import httpx

from .exceptions import VorimError
from .types import (
    Agent,
    AgentRegistrationInput,
    AgentRegistrationResult,
    AuditEventInput,
    AuditEventType,
    AuditResult,
    GrantPermissionOptions,
    PermissionCheckResult,
    PermissionScope,
    RateLimit,
    TrustRecord,
)


class Vorim:
    """Synchronous Vorim API client.

    Args:
        api_key: Your Vorim API key (``agid_sk_live_...`` or ``agid_sk_test_...``).
        base_url: API base URL. Defaults to ``https://api.vorim.ai``.
        timeout: Request timeout in seconds. Defaults to ``10``.
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.vorim.ai",
        timeout: float = 10,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url.rstrip("/") + "/v1"
        self._client = httpx.Client(
            base_url=self._base_url,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "vorim-python/2.3.0",
            },
            timeout=timeout,
        )

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._client.close()

    def __enter__(self) -> Vorim:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()

    # ── Health Check ─────────────────────────────────────────────────────

    def ping(self) -> dict[str, Any]:
        """Ping the Vorim API to verify connectivity.

        Returns ``{"status": "healthy", "timestamp": "..."}`` on success.
        Raises :class:`VorimError` if the API is unreachable.
        """
        resp = self._client.get(self._base_url.replace("/v1", "") + "/health")
        return self._handle(resp)

    # ── Agent Identity ────────────────────────────────────────────────────

    def register(
        self,
        name: str,
        capabilities: list[str],
        scopes: list[PermissionScope],
        description: str | None = None,
    ) -> AgentRegistrationResult:
        """Register a new agent with Vorim AI.

        Returns the agent identity and a private key (shown once — store it securely).
        """
        body: dict[str, Any] = {
            "name": name,
            "capabilities": capabilities,
            "scopes": scopes,
        }
        if description is not None:
            body["description"] = description

        data = self._post("/agents", body)
        return AgentRegistrationResult(
            agent=_parse_agent(data["agent"]),
            private_key=data["private_key"],
            public_key=data["public_key"],
            key_fingerprint=data["key_fingerprint"],
        )

    def verify(self, agent_id: str) -> TrustRecord:
        """Verify an agent's identity via the public Trust API.

        No authentication required. Cached for 60 seconds.
        """
        data = self._get(f"/trust/verify/{agent_id}")
        return TrustRecord(
            agent_id=data["agent_id"],
            owner=data["owner"],
            trust_score=data["trust_score"],
            status=data["status"],
            created_at=data["created_at"],
            active_scopes=data["active_scopes"],
            key_fingerprint=data["key_fingerprint"],
            revocation_status=data["revocation_status"],
            last_active=data.get("last_active"),
        )

    def get_agent(self, agent_id: str) -> Agent:
        """Get agent details."""
        data = self._get(f"/agents/{agent_id}")
        return _parse_agent(data)

    def list_agents(
        self,
        page: int | None = None,
        per_page: int | None = None,
        status: str | None = None,
    ) -> dict[str, Any]:
        """List all agents in the organisation.

        Returns ``{"agents": [...], "meta": {...}}``.
        """
        params: dict[str, Any] = {}
        if page is not None:
            params["page"] = page
        if per_page is not None:
            params["per_page"] = per_page
        if status is not None:
            params["status"] = status
        return self._get("/agents", params=params)

    def update_agent(
        self,
        agent_id: str,
        name: str | None = None,
        description: str | None = None,
        status: str | None = None,
        capabilities: list[str] | None = None,
    ) -> Agent:
        """Update an agent's metadata."""
        body: dict[str, Any] = {}
        if name is not None:
            body["name"] = name
        if description is not None:
            body["description"] = description
        if status is not None:
            body["status"] = status
        if capabilities is not None:
            body["capabilities"] = capabilities
        data = self._patch(f"/agents/{agent_id}", body)
        return _parse_agent(data)

    def revoke(self, agent_id: str) -> None:
        """Revoke an agent (permanent deactivation)."""
        self._delete(f"/agents/{agent_id}")

    # ── Permissions ───────────────────────────────────────────────────────

    def check(self, agent_id: str, scope: PermissionScope) -> PermissionCheckResult:
        """Check if an agent has a specific permission scope.

        Target: <5ms response via Redis cache.
        """
        data = self._post(f"/agents/{agent_id}/permissions/verify", {"scope": scope})
        return PermissionCheckResult(
            allowed=data["allowed"],
            scope=data["scope"],
            agent_id=data["agent_id"],
            reason=data.get("reason"),
            remaining_quota=data.get("remaining_quota"),
        )

    def grant(
        self,
        agent_id: str,
        scope: PermissionScope,
        valid_until: str | None = None,
        rate_limit: RateLimit | None = None,
    ) -> dict[str, Any]:
        """Grant a permission scope to an agent."""
        body: dict[str, Any] = {"scope": scope}
        if valid_until is not None:
            body["valid_until"] = valid_until
        if rate_limit is not None:
            body["rate_limit"] = asdict(rate_limit)
        return self._post(f"/agents/{agent_id}/permissions", body)

    def list_permissions(self, agent_id: str) -> list[dict[str, Any]]:
        """List all active permissions for an agent."""
        return self._get(f"/agents/{agent_id}/permissions")

    def revoke_permission(self, agent_id: str, scope: PermissionScope) -> dict[str, Any]:
        """Revoke a specific permission scope from an agent."""
        return self._delete(f"/agents/{agent_id}/permissions/{scope}")

    # ── Audit ─────────────────────────────────────────────────────────────

    def emit(
        self,
        agent_id: str,
        event_type: AuditEventType,
        action: str,
        result: AuditResult,
        resource: str | None = None,
        input_hash: str | None = None,
        output_hash: str | None = None,
        permission: PermissionScope | None = None,
        latency_ms: int | None = None,
        error_code: str | None = None,
        signature: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Emit a single audit event."""
        event = _build_event(
            agent_id=agent_id,
            event_type=event_type,
            action=action,
            result=result,
            resource=resource,
            input_hash=input_hash,
            output_hash=output_hash,
            permission=permission,
            latency_ms=latency_ms,
            error_code=error_code,
            signature=signature,
            metadata=metadata,
        )
        return self._post("/audit/events", {"events": [event]})

    def emit_batch(self, events: list[AuditEventInput]) -> dict[str, Any]:
        """Emit a batch of audit events (up to 1,000)."""
        raw = [_event_to_dict(e) for e in events]
        return self._post("/audit/events", {"events": raw})

    def export_audit(self, from_date: str, to_date: str, format: str = "json") -> dict[str, Any]:
        """Export a signed audit bundle for a date range."""
        return self._post("/audit/export", {"from": from_date, "to": to_date, "format": format})

    # ── API Keys ──────────────────────────────────────────────────────────

    def list_api_keys(self) -> list[dict[str, Any]]:
        """List all API keys for the organisation."""
        return self._get("/api-keys")

    def create_api_key(self, name: str, scopes: list[str] | None = None, expires_at: str | None = None) -> dict[str, Any]:
        """Create a new API key."""
        body: dict[str, Any] = {"name": name}
        if scopes is not None:
            body["scopes"] = scopes
        if expires_at is not None:
            body["expires_at"] = expires_at
        return self._post("/api-keys", body)

    def delete_api_key(self, key_id: str) -> dict[str, Any]:
        """Revoke an API key."""
        return self._delete(f"/api-keys/{key_id}")

    # ── HTTP Layer ────────────────────────────────────────────────────────

    def _get(self, path: str, params: dict[str, Any] | None = None) -> Any:
        resp = self._client.get(path, params=params)
        return self._handle(resp)

    def _post(self, path: str, body: Any) -> Any:
        resp = self._client.post(path, json=body)
        return self._handle(resp)

    def _patch(self, path: str, body: Any) -> Any:
        resp = self._client.patch(path, json=body)
        return self._handle(resp)

    def _delete(self, path: str) -> Any:
        resp = self._client.delete(path)
        return self._handle(resp)

    def _handle(self, resp: httpx.Response) -> Any:
        if not resp.is_success:
            try:
                err = resp.json().get("error", {})
            except Exception:
                err = {}
            raise VorimError(
                status=resp.status_code,
                code=err.get("code", "UNKNOWN_ERROR"),
                message=err.get("message", f"HTTP {resp.status_code}"),
                details=err.get("details"),
            )
        data = resp.json()
        return data.get("data", data)


class AsyncVorim:
    """Asynchronous Vorim API client.

    Same interface as :class:`Vorim` but all methods are ``async``.

    Usage::

        async with AsyncVorim(api_key="agid_sk_live_...") as vorim:
            result = await vorim.register(...)
    """

    def __init__(
        self,
        api_key: str,
        base_url: str = "https://api.vorim.ai",
        timeout: float = 10,
    ) -> None:
        self._api_key = api_key
        self._base_url = base_url.rstrip("/") + "/v1"
        self._client = httpx.AsyncClient(
            base_url=self._base_url,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
                "User-Agent": "vorim-python/2.3.0",
            },
            timeout=timeout,
        )

    async def close(self) -> None:
        await self._client.aclose()

    async def __aenter__(self) -> AsyncVorim:
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()

    # ── Health Check ─────────────────────────────────────────────────────

    async def ping(self) -> dict[str, Any]:
        """Ping the Vorim API to verify connectivity."""
        resp = await self._client.get(self._base_url.replace("/v1", "") + "/health")
        return self._handle(resp)

    # ── Agent Identity ────────────────────────────────────────────────────

    async def register(
        self,
        name: str,
        capabilities: list[str],
        scopes: list[PermissionScope],
        description: str | None = None,
    ) -> AgentRegistrationResult:
        body: dict[str, Any] = {
            "name": name,
            "capabilities": capabilities,
            "scopes": scopes,
        }
        if description is not None:
            body["description"] = description
        data = await self._post("/agents", body)
        return AgentRegistrationResult(
            agent=_parse_agent(data["agent"]),
            private_key=data["private_key"],
            public_key=data["public_key"],
            key_fingerprint=data["key_fingerprint"],
        )

    async def verify(self, agent_id: str) -> TrustRecord:
        data = await self._get(f"/trust/verify/{agent_id}")
        return TrustRecord(
            agent_id=data["agent_id"],
            owner=data["owner"],
            trust_score=data["trust_score"],
            status=data["status"],
            created_at=data["created_at"],
            active_scopes=data["active_scopes"],
            key_fingerprint=data["key_fingerprint"],
            revocation_status=data["revocation_status"],
            last_active=data.get("last_active"),
        )

    async def get_agent(self, agent_id: str) -> Agent:
        data = await self._get(f"/agents/{agent_id}")
        return _parse_agent(data)

    async def list_agents(
        self,
        page: int | None = None,
        per_page: int | None = None,
        status: str | None = None,
    ) -> dict[str, Any]:
        params: dict[str, Any] = {}
        if page is not None:
            params["page"] = page
        if per_page is not None:
            params["per_page"] = per_page
        if status is not None:
            params["status"] = status
        return await self._get("/agents", params=params)

    async def update_agent(
        self,
        agent_id: str,
        name: str | None = None,
        description: str | None = None,
        status: str | None = None,
        capabilities: list[str] | None = None,
    ) -> Agent:
        body: dict[str, Any] = {}
        if name is not None:
            body["name"] = name
        if description is not None:
            body["description"] = description
        if status is not None:
            body["status"] = status
        if capabilities is not None:
            body["capabilities"] = capabilities
        data = await self._patch(f"/agents/{agent_id}", body)
        return _parse_agent(data)

    async def revoke(self, agent_id: str) -> None:
        await self._delete(f"/agents/{agent_id}")

    # ── Permissions ───────────────────────────────────────────────────────

    async def check(self, agent_id: str, scope: PermissionScope) -> PermissionCheckResult:
        data = await self._post(f"/agents/{agent_id}/permissions/verify", {"scope": scope})
        return PermissionCheckResult(
            allowed=data["allowed"],
            scope=data["scope"],
            agent_id=data["agent_id"],
            reason=data.get("reason"),
            remaining_quota=data.get("remaining_quota"),
        )

    async def grant(
        self,
        agent_id: str,
        scope: PermissionScope,
        valid_until: str | None = None,
        rate_limit: RateLimit | None = None,
    ) -> dict[str, Any]:
        body: dict[str, Any] = {"scope": scope}
        if valid_until is not None:
            body["valid_until"] = valid_until
        if rate_limit is not None:
            body["rate_limit"] = asdict(rate_limit)
        return await self._post(f"/agents/{agent_id}/permissions", body)

    async def list_permissions(self, agent_id: str) -> list[dict[str, Any]]:
        return await self._get(f"/agents/{agent_id}/permissions")

    async def revoke_permission(self, agent_id: str, scope: PermissionScope) -> dict[str, Any]:
        return await self._delete(f"/agents/{agent_id}/permissions/{scope}")

    # ── Audit ─────────────────────────────────────────────────────────────

    async def emit(
        self,
        agent_id: str,
        event_type: AuditEventType,
        action: str,
        result: AuditResult,
        resource: str | None = None,
        input_hash: str | None = None,
        output_hash: str | None = None,
        permission: PermissionScope | None = None,
        latency_ms: int | None = None,
        error_code: str | None = None,
        signature: str | None = None,
        metadata: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        event = _build_event(
            agent_id=agent_id,
            event_type=event_type,
            action=action,
            result=result,
            resource=resource,
            input_hash=input_hash,
            output_hash=output_hash,
            permission=permission,
            latency_ms=latency_ms,
            error_code=error_code,
            signature=signature,
            metadata=metadata,
        )
        return await self._post("/audit/events", {"events": [event]})

    async def emit_batch(self, events: list[AuditEventInput]) -> dict[str, Any]:
        raw = [_event_to_dict(e) for e in events]
        return await self._post("/audit/events", {"events": raw})

    async def export_audit(self, from_date: str, to_date: str, format: str = "json") -> dict[str, Any]:
        return await self._post("/audit/export", {"from": from_date, "to": to_date, "format": format})

    # ── API Keys ──────────────────────────────────────────────────────────

    async def list_api_keys(self) -> list[dict[str, Any]]:
        return await self._get("/api-keys")

    async def create_api_key(self, name: str, scopes: list[str] | None = None, expires_at: str | None = None) -> dict[str, Any]:
        body: dict[str, Any] = {"name": name}
        if scopes is not None:
            body["scopes"] = scopes
        if expires_at is not None:
            body["expires_at"] = expires_at
        return await self._post("/api-keys", body)

    async def delete_api_key(self, key_id: str) -> dict[str, Any]:
        return await self._delete(f"/api-keys/{key_id}")

    # ── HTTP Layer ────────────────────────────────────────────────────────

    async def _get(self, path: str, params: dict[str, Any] | None = None) -> Any:
        resp = await self._client.get(path, params=params)
        return self._handle(resp)

    async def _post(self, path: str, body: Any) -> Any:
        resp = await self._client.post(path, json=body)
        return self._handle(resp)

    async def _patch(self, path: str, body: Any) -> Any:
        resp = await self._client.patch(path, json=body)
        return self._handle(resp)

    async def _delete(self, path: str) -> Any:
        resp = await self._client.delete(path)
        return self._handle(resp)

    def _handle(self, resp: httpx.Response) -> Any:
        if not resp.is_success:
            try:
                err = resp.json().get("error", {})
            except Exception:
                err = {}
            raise VorimError(
                status=resp.status_code,
                code=err.get("code", "UNKNOWN_ERROR"),
                message=err.get("message", f"HTTP {resp.status_code}"),
                details=err.get("details"),
            )
        data = resp.json()
        return data.get("data", data)


# ── Helpers ───────────────────────────────────────────────────────────────


def _parse_agent(data: dict[str, Any]) -> Agent:
    return Agent(
        id=data["id"],
        agent_id=data["agent_id"],
        org_id=data["org_id"],
        owner_user_id=data["owner_user_id"],
        name=data["name"],
        status=data["status"],
        key_fingerprint=data["key_fingerprint"],
        trust_score=data["trust_score"],
        capabilities=data["capabilities"],
        metadata=data.get("metadata", {}),
        created_at=data["created_at"],
        updated_at=data["updated_at"],
        description=data.get("description"),
        expires_at=data.get("expires_at"),
        revoked_at=data.get("revoked_at"),
        revoked_by=data.get("revoked_by"),
    )


def _build_event(**kwargs: Any) -> dict[str, Any]:
    return {k: v for k, v in kwargs.items() if v is not None}


def _event_to_dict(event: AuditEventInput) -> dict[str, Any]:
    return {k: v for k, v in asdict(event).items() if v is not None}
