from __future__ import annotations

import asyncio
import logging
import time
from typing import Any, Dict, Optional

import httpx
import requests

from ._utils import compute_retry_delay, redact_payload
from ._version import __version__
from .errors import (
    NetworkError,
    RequestTimeout,
    build_error_from_http,
)

logger = logging.getLogger("its_ai")

DEFAULT_TIMEOUT = 10.0
DEFAULT_HOST = "https://api.its-ai.org"
USER_AGENT = f"its-ai-python-sdk/{__version__}"


def _headers(api_key: str) -> Dict[str, str]:
    return {
        "User-Agent": USER_AGENT,
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": f"APIKey {api_key}"
    }


def post_with_attempts(
        session: requests.Session,
        url: str,
        json_body: Dict[str, Any],
        timeout: Optional[float],
        api_key: str,
        max_attempts: int = 3,
) -> requests.Response:
    attempt_limit = max(1, int(max_attempts))
    for attempt in range(attempt_limit):
        try:
            response = session.post(
                url,
                headers=_headers(api_key),
                json=json_body,
                timeout=timeout or DEFAULT_TIMEOUT,
            )
            if response.status_code in (429,) or 500 <= response.status_code < 600:
                if attempt < attempt_limit - 1:
                    retry_after = response.headers.get("Retry-After")
                    delay = compute_retry_delay(attempt, retry_after)
                    logger.debug(
                        "retryable HTTP %s, retrying in %.2fs url=%s payload=%s",
                        response.status_code,
                        delay,
                        url,
                        redact_payload(json_body),
                    )
                    time.sleep(delay)
                    continue

            return response
        except requests.exceptions.Timeout as e:
            if attempt < attempt_limit - 1:
                delay = compute_retry_delay(attempt, None)
                logger.debug("timeout, retrying in %.2fs", delay)
                time.sleep(delay)
                continue
            raise RequestTimeout(status_code=None, type="request_timeout", message=str(e)) from e
        except requests.exceptions.RequestException as e:
            raise NetworkError(status_code=None, type="network_error", message=str(e)) from e
    # Unreachable code
    raise NotImplementedError()


async def apost_with_attempts(
        client: httpx.AsyncClient,
        url: str,
        json_body: Dict[str, Any],
        timeout: Optional[float],
        api_key: str,
        max_attempts: int = 3,
) -> httpx.Response:
    attempt_limit = max(1, int(max_attempts))
    for attempt in range(attempt_limit):
        try:
            response = await client.post(
                url,
                headers=_headers(api_key),
                json=json_body,
                timeout=timeout or DEFAULT_TIMEOUT,
            )
            if response.status_code in (429,) or 500 <= response.status_code < 600:
                if attempt < attempt_limit - 1:
                    retry_after = response.headers.get("Retry-After")
                    delay = compute_retry_delay(attempt, retry_after)
                    logger.debug(
                        "retryable HTTP %s, retrying in %.2fs url=%s payload=%s",
                        response.status_code,
                        delay,
                        url,
                        redact_payload(json_body),
                    )
                    await asyncio.sleep(delay)
                    continue

            return response
        except httpx.TimeoutException as e:
            if attempt < attempt_limit - 1:
                delay = compute_retry_delay(attempt, None)
                logger.debug("timeout, retrying in %.2fs", delay)
                await asyncio.sleep(delay)
                continue
            raise RequestTimeout(status_code=None, type="request_timeout", message=str(e)) from e
        except httpx.HTTPError as e:
            raise NetworkError(status_code=None, type="network_error", message=str(e)) from e

    # Unreachable code
    raise NotImplementedError()


def parse_json_response(response: requests.Response) -> Any:
    try:
        return response.json()
    except ValueError:
        return None


async def aparse_json_response(response: httpx.Response) -> Any:
    try:
        return response.json()
    except ValueError:
        return None


def handle_response(response: requests.Response) -> Any:
    if 200 <= response.status_code < 300:
        return parse_json_response(response)

    try:
        body_text = response.text
    except Exception:
        body_text = None

    parsed = parse_json_response(response)
    json_body = parsed if isinstance(parsed, dict) else None
    raise build_error_from_http(response.status_code, body_text, json_body)


async def ahandle_response(response: httpx.Response) -> Any:
    if 200 <= response.status_code < 300:
        return await aparse_json_response(response)

    try:
        body_text = response.text
    except Exception:
        body_text = None

    parsed = await aparse_json_response(response)
    json_body = parsed if isinstance(parsed, dict) else None
    raise build_error_from_http(response.status_code, body_text, json_body)
