"""HTTP client for Manta API."""

from __future__ import annotations

import json
from collections.abc import AsyncIterator
from typing import Any

import httpx

from manta_client.config import config_manager


class APIError(Exception):
    """API error with status code and message."""

    def __init__(self, code: int, message: str, data: Any = None):
        self.code = code
        self.message = message
        self.data = data
        super().__init__(f"[{code}] {message}")


class MantaClient:
    """HTTP client for Manta API.

    Handles:
    - Authentication via Bearer token
    - Response format: {code, msg, data}
    - Error handling
    - SSE streaming
    """

    def __init__(self, base_url: str | None = None, token: str | None = None):
        """Initialize client.

        Args:
            base_url: API base URL (defaults to config value)
            token: Access token (defaults to config value)
        """
        config = config_manager.load_config()
        self.base_url = (base_url or config.api_url).rstrip("/")
        self.token = token or config.access_token
        self._refresh_token_value = config.refresh_token
        self._client: httpx.AsyncClient | None = None
        self._refreshing = False

    def _get_headers(self) -> dict[str, str]:
        """Get request headers with authentication."""
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
        }
        if self.token:
            headers["Authorization"] = f"Bearer {self.token}"
        return headers

    async def _get_client(self) -> httpx.AsyncClient:
        """Get or create async HTTP client."""
        if self._client is None or self._client.is_closed:
            self._client = httpx.AsyncClient(
                base_url=self.base_url,
                headers=self._get_headers(),
                timeout=30.0,
            )
        return self._client

    async def close(self) -> None:
        """Close the HTTP client."""
        if self._client and not self._client.is_closed:
            await self._client.aclose()

    async def _try_refresh_token(self) -> bool:
        """Attempt to refresh the access token using the refresh token.

        Returns True if refresh succeeded, False otherwise.
        """
        if self._refreshing or not self._refresh_token_value:
            return False

        self._refreshing = True
        try:
            async with httpx.AsyncClient(base_url=self.base_url, timeout=10.0) as client:
                resp = await client.post(
                    "/api/v1/auth/refresh",
                    json={"refresh_token": self._refresh_token_value},
                )
                if resp.status_code != 200:
                    return False

                result = resp.json()
                if result.get("code") != 200:
                    return False

                data = result.get("data", {})
                new_access = data.get("access_token", "")
                new_refresh = data.get("refresh_token", "")

                if not new_access:
                    return False

                # Update in-memory state
                self.token = new_access
                if new_refresh:
                    self._refresh_token_value = new_refresh

                # Persist to config
                config = config_manager.load_config()
                config.access_token = new_access
                if new_refresh:
                    config.refresh_token = new_refresh
                config_manager.save_config(config)

                # Recreate HTTP client with new token
                if self._client and not self._client.is_closed:
                    await self._client.aclose()
                self._client = None

                return True
        except Exception:
            return False
        finally:
            self._refreshing = False

    def _parse_response(self, response: httpx.Response) -> Any:
        """Parse API response.

        The API returns: {code: int, msg: str, data: Any}
        - code 200 means success
        - Other codes are errors
        """
        if response.status_code == 401:
            raise APIError(401, "Unauthorized. Please login with 'manta-client auth login'")

        if response.status_code >= 500:
            raise APIError(response.status_code, f"Server error: {response.text}")

        try:
            result = response.json()
        except json.JSONDecodeError as e:
            raise APIError(response.status_code, f"Invalid JSON response: {response.text}") from e

        code = result.get("code", response.status_code)
        msg = result.get("msg", "Unknown error")
        data = result.get("data")

        if code != 200:
            raise APIError(code, msg, data)

        return data

    async def get(self, path: str, params: dict[str, Any] | None = None) -> Any:
        """Make GET request.

        Args:
            path: API path (e.g., '/api/v1/agents')
            params: Query parameters

        Returns:
            Response data
        """
        client = await self._get_client()
        response = await client.get(path, params=params)
        if response.status_code == 401 and await self._try_refresh_token():
            client = await self._get_client()
            response = await client.get(path, params=params)
        return self._parse_response(response)

    async def post(
        self,
        path: str,
        data: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
        timeout: float | None = None,
    ) -> Any:
        """Make POST request.

        Args:
            path: API path
            data: Request body
            params: Query parameters
            timeout: Request timeout in seconds (None uses client default)

        Returns:
            Response data
        """
        client = await self._get_client()
        response = await client.post(path, json=data, params=params, timeout=timeout)
        if response.status_code == 401 and await self._try_refresh_token():
            client = await self._get_client()
            response = await client.post(path, json=data, params=params, timeout=timeout)
        return self._parse_response(response)

    async def put(
        self,
        path: str,
        data: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
    ) -> Any:
        """Make PUT request.

        Args:
            path: API path
            data: Request body
            params: Query parameters

        Returns:
            Response data
        """
        client = await self._get_client()
        response = await client.put(path, json=data, params=params)
        if response.status_code == 401 and await self._try_refresh_token():
            client = await self._get_client()
            response = await client.put(path, json=data, params=params)
        return self._parse_response(response)

    async def delete(self, path: str, params: dict[str, Any] | None = None) -> Any:
        """Make DELETE request.

        Args:
            path: API path
            params: Query parameters

        Returns:
            Response data
        """
        client = await self._get_client()
        response = await client.delete(path, params=params)
        if response.status_code == 401 and await self._try_refresh_token():
            client = await self._get_client()
            response = await client.delete(path, params=params)
        return self._parse_response(response)

    async def upload(
        self,
        path: str,
        file_content: bytes,
        file_name: str,
        fields: dict[str, str] | None = None,
        timeout: float | None = None,
    ) -> Any:
        """Make multipart/form-data POST request for file upload.

        Args:
            path: API path
            file_content: File bytes to upload
            file_name: File name for the upload
            fields: Additional form fields
            timeout: Request timeout in seconds

        Returns:
            Response data
        """
        url = f"{self.base_url}{path}"
        headers: dict[str, str] = {}
        if self.token:
            headers["Authorization"] = f"Bearer {self.token}"

        files = {"archive": (file_name, file_content, "application/gzip")}
        async with httpx.AsyncClient(timeout=timeout or 60.0) as client:
            response = await client.post(url, files=files, data=fields or {}, headers=headers)

        if response.status_code == 401 and await self._try_refresh_token():
            headers["Authorization"] = f"Bearer {self.token}"
            async with httpx.AsyncClient(timeout=timeout or 60.0) as client:
                response = await client.post(url, files=files, data=fields or {}, headers=headers)

        return self._parse_response(response)

    async def stream_sse(
        self, path: str, params: dict[str, Any] | None = None
    ) -> AsyncIterator[dict[str, Any]]:
        """Stream Server-Sent Events.

        Args:
            path: API path
            params: Query parameters (token will be added automatically)

        Yields:
            Parsed SSE event data
        """
        # Add token to params for SSE (EventSource doesn't support headers)
        if params is None:
            params = {}
        if self.token:
            params["token"] = self.token

        url = f"{self.base_url}{path}"

        async with httpx.AsyncClient(timeout=None) as client:
            async with client.stream("GET", url, params=params) as response:
                if response.status_code == 401:
                    raise APIError(401, "Unauthorized")

                async for line in response.aiter_lines():
                    if line.startswith("data: "):
                        data_str = line[6:]  # Remove "data: " prefix
                        try:
                            data = json.loads(data_str)
                            yield data
                        except json.JSONDecodeError:
                            continue

    async def download(self, path: str, params: dict[str, Any] | None = None) -> bytes:
        """Download file content.

        Args:
            path: API path
            params: Query parameters

        Returns:
            File content as bytes
        """
        # Add token to params for download
        if params is None:
            params = {}
        if self.token:
            params["token"] = self.token
        # Don't follow redirects, download directly
        params["redirect"] = "false"

        client = await self._get_client()
        response = await client.get(path, params=params, follow_redirects=True)

        if response.status_code == 401:
            raise APIError(401, "Unauthorized")
        if response.status_code == 404:
            raise APIError(404, "Not found")
        if response.status_code >= 400:
            raise APIError(response.status_code, response.text)

        return response.content


# Singleton client instance
_client: MantaClient | None = None


def get_client() -> MantaClient:
    """Get or create the global client instance."""
    global _client
    if _client is None:
        _client = MantaClient()
    return _client


def reset_client() -> None:
    """Reset the global client instance (e.g., after config change)."""
    global _client
    _client = None
