from __future__ import annotations

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

import httpx
import requests

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


logger = logging.getLogger("its_ai")


DEFAULT_TIMEOUT = 10.0
USER_AGENT = "its-ai-python-sdk/0.1.0"


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


def _prepare_body(body: Dict[str, Any]) -> Tuple[str, bytes]:
    content = json.dumps(body, separators=(",", ":")).encode("utf-8")
    return "application/json", content


def post_with_retries(
    session: requests.Session,
    url: str,
    json_body: Dict[str, Any],
    timeout: Optional[float],
    max_retries: int = 3,
) -> requests.Response:
    attempts = max(1, max_retries)
    for attempt in range(attempts):
        try:
            response = session.post(
                url,
                headers=_headers(),
                json=json_body,
                timeout=timeout or DEFAULT_TIMEOUT,
            )
        except requests.exceptions.Timeout as e:
            if attempt < attempts - 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

        if response.status_code in (429,) or 500 <= response.status_code < 600:
            if attempt < attempts - 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

    return response


async def apost_with_retries(
    client: httpx.AsyncClient,
    url: str,
    json_body: Dict[str, Any],
    timeout: Optional[float],
    max_retries: int = 3,
) -> httpx.Response:
    attempts = max(1, max_retries)
    for attempt in range(attempts):
        try:
            response = await client.post(
                url,
                headers=_headers(),
                json=json_body,
                timeout=timeout or DEFAULT_TIMEOUT,
            )
        except httpx.TimeoutException as e:
            if attempt < attempts - 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

        if response.status_code in (429,) or 500 <= response.status_code < 600:
            if attempt < attempts - 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

    return response


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:
        data = parse_json_response(response)
        return data
    body_text = None
    try:
        body_text = response.text
    except Exception:
        body_text = None
    json_body = None
    try:
        json_body = response.json()
    except ValueError:
        json_body = 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:
        try:
            return response.json()
        except ValueError:
            return None
    body_text = None
    try:
        body_text = response.text
    except Exception:
        body_text = None
    json_body = None
    try:
        json_body = response.json()
    except ValueError:
        json_body = None
    raise build_error_from_http(response.status_code, body_text, json_body)


