from __future__ import annotations

import json
import logging
from typing import Any, Dict, Optional

import requests
from requests import Response, Session
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from urllib.parse import urljoin

from app.services.appwrite_client import AppWriteClient
from app.utils.logging import get_logger

logger = get_logger(__file__)


class CoCApi:
    """
    Centralized API client for Code of Conquest.
    All HTTP interactions go through _request() for consistent behavior and logging.
    """

    def __init__(
        self,
        base_url: str = "http://localhost:8000",
        default_timeout: tuple[float, float] = (5.0, 10.0),
        max_retries: int = 3,
    ) -> None:
        """
        :param base_url: Base URL for the API (no trailing slash needed).
        :param default_timeout: (connect_timeout, read_timeout)
        :param max_retries: Number of retries for transient network/server errors.
        """
        self.base_url = base_url.rstrip("/")
        self.default_timeout = default_timeout
        self._aw = AppWriteClient()

        # Base headers for JSON APIs.
        self._base_headers: Dict[str, str] = {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "User-Agent": "CoC-Client/1.0",
        }

        # Pre-configured Session with retries.
        self._session = self._build_session(max_retries=max_retries)

    # ---------- Public convenience methods ----------

    def create_char(self, name:str, origin_story:str, race_id:str, profession_id:str) -> Dict[str, Any]:
        payload = {
            "name":name,
            "origin_story":origin_story,
            "race_id":race_id,
            "profession_id":profession_id
        }
        result = self.post("/char/new",payload=payload)
        player_uuid = result.get("result")
        return player_uuid

    def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        return self._request("GET", path, params=params)

    def post(self, path: str, payload: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        return self._request("POST", path, json_body=payload)

    def patch(self, path: str, json_body: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
        return self._request("PATCH", path, json_body=json_body)

    # ---------- Internal helpers ----------

    def _build_session(self, max_retries: int) -> Session:
        """
        Create a Session with sane retries for transient network/server failures.
        We retry idempotent methods and some 5xx responses.
        """
        session = requests.Session()

        retries = Retry(
            total=max_retries,
            connect=max_retries,
            read=max_retries,
            backoff_factor=0.5,
            status_forcelist=(502, 503, 504),
            allowed_methods=frozenset(["GET", "HEAD", "OPTIONS", "TRACE"]),
            raise_on_status=False,
        )

        adapter = HTTPAdapter(max_retries=retries)
        session.mount("http://", adapter)
        session.mount("https://", adapter)
        return session

    def _mint_jwt(self) -> str:
        """Mint a JWT from AppWrite; empty string if unavailable (don’t block requests)."""
        try:
            token = self._aw.mint_jwt()  # expected to return dict-like
            return token.get("jwt", "") if token else ""
        except Exception as e:
            logger.warning("Failed to mint JWT", extra={"error": str(e)})
            return ""

    def _auth_headers(self) -> Dict[str, str]:
        """
        Build per-call headers with Authorization if a JWT is available.
        Avoid mutating shared headers.
        """
        headers = dict(self._base_headers)
        jwt = self._mint_jwt()
        if jwt:
            headers["Authorization"] = f"Bearer {jwt}"
        return headers

    def _resolve_url(self, path_or_url: str) -> str:
        """
        Accept either a full URL or a path (e.g., '/char/'). Join with base_url when needed.
        """
        if path_or_url.lower().startswith(("http://", "https://")):
            return path_or_url
        return urljoin(self.base_url + "/", path_or_url.lstrip("/"))

    def _safe_json(self, resp: Response) -> Dict[str, Any]:
        """
        Attempt to parse JSON. If body is empty or not JSON, return {}.
        """
        # 204 No Content or truly empty payloads
        if resp.status_code == 204 or not resp.content:
            return {}

        try:
            return resp.json()
        except ValueError:
            # Non-JSON payload; log preview and return empty.
            preview = ""
            try:
                preview = resp.text[:400]
            except Exception:
                pass
            logger.warning(
                "Non-JSON response body",
                extra={
                    "url": resp.request.url if resp.request else None,
                    "status": resp.status_code,
                    "body_preview": preview,
                },
            )
            return {}

    def _request(
        self,
        method: str,
        path: str,
        *,
        params: Optional[Dict[str, Any]] = None,
        json_body: Optional[Dict[str, Any]] = None,
        timeout: Optional[tuple[float, float]] = None,
    ) -> Dict[str, Any]:
        """
        Central request executor. Never raises to the caller.
        Returns parsed JSON on success or {} on any error.
        """
        url = self._resolve_url(path)
        headers = self._auth_headers()
        to = timeout or self.default_timeout

        try:
            resp = self._session.request(
                method=method.upper(),
                url=url,
                headers=headers,
                params=params,
                json=json_body,
                timeout=to,
            )

            # Log and return {} on non-2xx
            if not (200 <= resp.status_code < 300):
                # Truncate body in logs to avoid huge entries
                preview = ""
                try:
                    preview = resp.text[:400]
                except Exception:
                    pass

                logger.warning(
                    "HTTP request failed",
                    extra={
                        "method": method.upper(),
                        "url": resp.request.url if resp.request else url,
                        "status": resp.status_code,
                        "params": params,
                        "json_body": json_body,
                        "body_preview": preview,
                    },
                )
                return {}

            # Success path: parse JSON safely
            return self._safe_json(resp)

        except requests.exceptions.RequestException as e:
            # Network/timeout/connection errors
            logger.warning(
                "Network error during HTTP request",
                extra={
                    "method": method.upper(),
                    "url": url,
                    "params": params,
                    "json_body": json_body,
                    "error_type": type(e).__name__,
                    "error": str(e),
                },
                exc_info=True,
            )
            return {}
        except Exception as e:
            # Absolute last-resort guardrail
            logger.error(
                "Unexpected error during HTTP request",
                extra={
                    "method": method.upper(),
                    "url": url,
                    "params": params,
                    "json_body": json_body,
                    "error_type": type(e).__name__,
                    "error": str(e),
                },
                exc_info=True,
            )
            return {}
