import json
import os
import time
from contextvars import ContextVar
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

from django.conf import settings


DEFAULT_TIMEOUT = 30
_api_debug_log = ContextVar("api_debug_log", default=None)


class ApiClientError(Exception):
    def __init__(self, message, status_code=None, response_body=None):
        super().__init__(message)
        self.status_code = status_code
        self.response_body = response_body


def reset_api_debug_log():
    _api_debug_log.set([])


def get_api_debug_log():
    return list(_api_debug_log.get() or [])


def _append_api_debug_log(entry):
    log = _api_debug_log.get()
    if log is not None:
        log.append(entry)


def _get_setting(name, default=None):
    return getattr(settings, name, None) or os.environ.get(name) or default


def _get_base_url():
    base_url = _get_setting("DASHBOARD_API_BASE_URL") or _get_setting("VITE_API_BASE_URL")

    if not base_url:
        raise ApiClientError(
            "Missing API base URL. Set DASHBOARD_API_BASE_URL or VITE_API_BASE_URL."
        )

    return base_url.rstrip("/")


def _get_bearer_token(bearer_token=None):
    return bearer_token or _get_setting("DASHBOARD_BEARER_TOKEN") or _get_setting("VITE_BEARER_TOKEN")


def _build_url(path, params=None):
    if path.startswith(("http://", "https://")):
        url = path
    else:
        url = f"{_get_base_url()}/{path.lstrip('/')}"

    if params:
        url = f"{url}?{urlencode(params)}"

    return url


def _request(method, path, payload=None, params=None, timeout=DEFAULT_TIMEOUT, bearer_token=None):
    body = None
    headers = {"Content-Type": "application/json"}
    token = _get_bearer_token(bearer_token=bearer_token)

    if token:
        headers["Authorization"] = f"Bearer {token}"

    if payload is not None:
        body = json.dumps(payload).encode("utf-8")

    url = _build_url(path, params=params)
    method = method.upper()
    started_at = time.perf_counter()
    debug_entry = {
        "method": method,
        "url": url,
        "payload": payload,
        "params": params,
        "has_token": bool(token),
    }
    print(
        "API REQUEST:",
        debug_entry,
        flush=True,
    )

    request = Request(
        url,
        data=body,
        headers=headers,
        method=method,
    )

    try:
        with urlopen(request, timeout=timeout) as response:
            response_body = response.read().decode("utf-8")
            debug_entry.update(
                {
                    "status": response.status,
                    "duration_ms": round((time.perf_counter() - started_at) * 1000),
                    "response": response_body,
                }
            )
            _append_api_debug_log(debug_entry)
            print(
                "API RESPONSE:",
                {
                    "method": method,
                    "url": url,
                    "status": response.status,
                },
                flush=True,
            )
    except HTTPError as error:
        response_body = error.read().decode("utf-8", errors="replace")
        debug_entry.update(
            {
                "status": error.code,
                "duration_ms": round((time.perf_counter() - started_at) * 1000),
                "response": response_body,
                "error": response_body,
            }
        )
        _append_api_debug_log(debug_entry)
        raise ApiClientError(
            f"API request failed with status {error.code}.",
            status_code=error.code,
            response_body=response_body,
        ) from error
    except URLError as error:
        debug_entry.update(
            {
                "duration_ms": round((time.perf_counter() - started_at) * 1000),
                "error": str(error.reason),
            }
        )
        _append_api_debug_log(debug_entry)
        raise ApiClientError(f"API request failed: {error.reason}") from error

    if not response_body:
        return None

    try:
        return json.loads(response_body)
    except json.JSONDecodeError as error:
        raise ApiClientError("API response is not valid JSON.", response_body=response_body) from error


def get(path, params=None, timeout=DEFAULT_TIMEOUT, bearer_token=None):
    return _request("GET", path, params=params, timeout=timeout, bearer_token=bearer_token)


def post(path, payload=None, timeout=DEFAULT_TIMEOUT, bearer_token=None):
    return _request("POST", path, payload=payload, timeout=timeout, bearer_token=bearer_token)
