"""High-level API client wrapper for Navari REST API."""

import json
import mimetypes
from pathlib import Path
from typing import Any
from urllib.parse import quote, urlparse

import httpx

from navari_cli.generated.navari_pydantic_ai_server_client import Client
from navari_cli.generated.navari_pydantic_ai_server_client.api.conversations import (
    create_conversation,
    delete_conversation,
    get_conversation,
    get_conversation_messages,
    list_conversations,
    update_conversation,
)
from navari_cli.generated.navari_pydantic_ai_server_client.api.files import (
    delete_file,
    get_file_metadata,
    list_files,
    upload_file,
)
from navari_cli.generated.navari_pydantic_ai_server_client.api.runs import cancel_run, get_all_runs, get_run_status
from navari_cli.generated.navari_pydantic_ai_server_client.api.user import get_user_profile
from navari_cli.generated.navari_pydantic_ai_server_client.models import (
    BodyUploadFileApiFilesPost,
    ConversationCreate,
    ConversationUpdate,
)
from navari_cli.generated.navari_pydantic_ai_server_client.types import File


class NavariAPIClient:
    """High-level wrapper around generated OpenAPI client."""

    def __init__(self, base_url: str, token: str, timeout: float = 30.0, verify_ssl: bool = True):
        """
        Initialize API client.

        Args:
            base_url: Base URL of Navari API (e.g., https://navari.example.com)
            token: JWT bearer token
            timeout: Request timeout in seconds
            verify_ssl: Whether to verify SSL certificates (set to False for self-signed certs)
        """
        self.base_url = base_url
        self.token = token
        self.verify_ssl = verify_ssl

        self.client = Client(
            base_url=base_url,
            headers={"Authorization": f"Bearer {token}"},
            timeout=httpx.Timeout(timeout),
            verify_ssl=verify_ssl,
        )

    # Conversation operations

    async def create_conversation(self, title: str = "New Conversation", user_id: str = "") -> dict[str, Any]:
        """Create new conversation."""
        body = ConversationCreate(user_id=user_id, title=title)
        response = await create_conversation.asyncio_detailed(client=self.client, body=body)

        if response.status_code == 200:
            return response.parsed.to_dict()
        raise httpx.HTTPStatusError(
            f"Failed to create conversation: {response.status_code}", request=response, response=response
        )

    async def list_conversations(self, limit: int = 20, offset: int = 0) -> list[dict[str, Any]]:
        """List conversations with pagination."""
        response = await list_conversations.asyncio_detailed(client=self.client, limit=limit, offset=offset)

        if response.status_code == 200:
            return [conv.to_dict() for conv in response.parsed.conversations]
        raise httpx.HTTPStatusError(
            f"Failed to list conversations: {response.status_code}", request=response, response=response
        )

    async def get_conversation(self, conversation_id: str) -> dict[str, Any]:
        """Get conversation details."""
        response = await get_conversation.asyncio_detailed(client=self.client, conversation_id=conversation_id)

        if response.status_code == 200:
            return response.parsed.to_dict()
        if response.status_code == 404:
            raise FileNotFoundError(f"Conversation '{conversation_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to get conversation: {response.status_code}", request=response, response=response
        )

    async def update_conversation(self, conversation_id: str, title: str) -> dict[str, Any]:
        """Update conversation title."""
        body = ConversationUpdate(title=title)
        response = await update_conversation.asyncio_detailed(
            client=self.client, conversation_id=conversation_id, body=body
        )

        if response.status_code == 200:
            return response.parsed.to_dict()
        if response.status_code == 404:
            raise FileNotFoundError(f"Conversation '{conversation_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to update conversation: {response.status_code}", request=response, response=response
        )

    async def delete_conversation(self, conversation_id: str) -> None:
        """Delete conversation."""
        response = await delete_conversation.asyncio_detailed(client=self.client, conversation_id=conversation_id)

        if response.status_code == 200:
            return
        if response.status_code == 404:
            raise FileNotFoundError(f"Conversation '{conversation_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to delete conversation: {response.status_code}", request=response, response=response
        )

    async def get_messages(self, conversation_id: str, limit: int = 10000000, offset: int = 0) -> list[dict[str, Any]]:
        """Get conversation messages as raw pydantic-ai events."""
        response = await get_conversation_messages.asyncio_detailed(
            client=self.client, conversation_id=conversation_id, limit=limit, offset=offset
        )

        if response.status_code == 200:
            # Generated client doesn't parse 200 for this endpoint (complex union type),
            # so we parse the raw JSON content ourselves
            return json.loads(response.content)
        if response.status_code == 404:
            raise FileNotFoundError(f"Conversation '{conversation_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to get messages: {response.status_code}", request=response, response=response
        )

    # File operations

    async def list_files(self, conversation_id: str) -> list[dict[str, Any]]:
        """List files in conversation."""
        response = await list_files.asyncio_detailed(client=self.client, conversation_id=conversation_id)

        if response.status_code == 200:
            return [f.to_dict() for f in response.parsed.files]
        raise httpx.HTTPStatusError(
            f"Failed to list files: {response.status_code}", request=response, response=response
        )

    async def upload_file(self, conversation_id: str, file_path: Path) -> dict[str, Any]:
        """Upload a file to a conversation."""
        mime_type = mimetypes.guess_type(str(file_path))[0] or "application/octet-stream"
        with open(file_path, "rb") as f:
            file_obj = File(payload=f, file_name=file_path.name, mime_type=mime_type)
            body = BodyUploadFileApiFilesPost(file=file_obj, conversation_id=conversation_id)
            response = await upload_file.asyncio_detailed(client=self.client, body=body)

        if response.status_code == 201:
            return response.parsed.to_dict()
        raise httpx.HTTPStatusError(
            f"Failed to upload file: {response.status_code}", request=response, response=response
        )

    async def get_file_metadata(self, file_id: str) -> dict[str, Any]:
        """Get file metadata by ID."""
        response = await get_file_metadata.asyncio_detailed(client=self.client, file_id=file_id)

        if response.status_code == 200:
            return response.parsed.to_dict()
        if response.status_code == 404:
            raise FileNotFoundError(f"File '{file_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to get file metadata: {response.status_code}", request=response, response=response
        )

    async def download_file(self, file_id: str) -> bytes:
        """Download file content by ID.

        Uses a direct httpx request because the generated client tries to
        parse the response as JSON, which fails for binary"""
        http_client = self.client.get_async_httpx_client()
        response = await http_client.request("get", f"/api/files/{quote(file_id, safe='')}/content")

        if response.status_code == 200:
            return response.content
        if response.status_code == 404:
            raise FileNotFoundError(f"File '{file_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to download file: {response.status_code}", request=response, response=response
        )

    async def delete_file(self, file_id: str) -> None:
        """Delete a file by ID."""
        response = await delete_file.asyncio_detailed(client=self.client, file_id=file_id)

        if response.status_code == 204:
            return
        if response.status_code == 404:
            raise FileNotFoundError(f"File '{file_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to delete file: {response.status_code}", request=response, response=response
        )

    async def download_workspace(self, conversation_id: str) -> bytes:
        """Download all workspace files for a conversation as a zip.

        The server returns a 303 redirect to a pre-signed storage URL.
        We handle this manually to avoid forwarding the Authorization header
        to the external storage service (which would cause a 403).
        """
        http_client = self.client.get_async_httpx_client()
        response = await http_client.request(
            "get",
            f"/api/conversations/{quote(conversation_id, safe='')}/files",
            follow_redirects=False,
        )

        if response.status_code in (301, 302, 303, 307, 308):
            redirect_url = response.headers.get("location")
            if redirect_url:
                orig_host = urlparse(self.base_url).hostname
                redirect_host = urlparse(redirect_url).hostname
                if redirect_host and redirect_host == orig_host:
                    response = await http_client.request("get", redirect_url, follow_redirects=True)
                else:
                    async with httpx.AsyncClient(verify=self.verify_ssl) as plain_client:
                        response = await plain_client.get(redirect_url)
                if response.status_code == 200:
                    return response.content
                body_preview = response.text[:300] if response.text else ""
                raise Exception(
                    f"Workspace download failed ({response.status_code}).\n"
                    f"  URL: {redirect_url[:120]}...\n"
                    f"  Body: {body_preview}"
                )

        if response.status_code == 200:
            return response.content
        if response.status_code == 404:
            raise FileNotFoundError(f"Conversation '{conversation_id}' not found")
        raise httpx.HTTPStatusError(
            f"Failed to download workspace: {response.status_code}", request=response, response=response
        )

    # Run operations

    async def list_runs(self) -> list[dict[str, Any]]:
        """List all"""
        response = await get_all_runs.asyncio_detailed(client=self.client)

        if response.status_code == 200:
            return response.parsed.to_dict() if response.parsed else json.loads(response.content)
        raise httpx.HTTPStatusError(f"Failed to list runs: {response.status_code}", request=response, response=response)

    async def get_run_status(self, conversation_id: str) -> dict[str, Any]:
        """Get run status for a conversation."""
        response = await get_run_status.asyncio_detailed(client=self.client, conversation_id=conversation_id)

        if response.status_code == 200:
            return response.parsed.to_dict() if response.parsed else json.loads(response.content)
        if response.status_code == 404:
            raise FileNotFoundError(f"No run found for conversation '{conversation_id}'")
        raise httpx.HTTPStatusError(
            f"Failed to get run status: {response.status_code}", request=response, response=response
        )

    async def cancel_run(self, conversation_id: str) -> None:
        """Cancel an active run for a conversation."""
        response = await cancel_run.asyncio_detailed(client=self.client, conversation_id=conversation_id)

        if response.status_code == 200:
            return
        if response.status_code == 404:
            raise FileNotFoundError(f"No active run for conversation '{conversation_id}'")
        raise httpx.HTTPStatusError(
            f"Failed to cancel run: {response.status_code}", request=response, response=response
        )

    # User operations

    async def get_user_profile(self) -> dict[str, Any]:
        """Get current user profile."""
        response = await get_user_profile.asyncio_detailed(client=self.client)

        if response.status_code == 200:
            return response.parsed.to_dict()
        if response.status_code == 401:
            raise PermissionError("Authentication failed - invalid token")
        raise httpx.HTTPStatusError(
            f"Failed to get user profile: {response.status_code}", request=response, response=response
        )

    # Context manager support

    async def __aenter__(self):
        """Async context manager entry."""
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit."""
        await self.close()

    async def close(self):
        """Close the HTTP client."""
        if hasattr(self.client, "_async_client") and self.client._async_client:
            await self.client._async_client.aclose()
