from __future__ import annotations

import asyncio
import math
from collections.abc import Awaitable, Callable
from dataclasses import dataclass

import httpx

from .models import RetryPolicy


@dataclass(slots=True)
class RangeSupport:
    supported: bool
    content_length: int | None


def build_segments(total_size: int, workers: int) -> list[tuple[int, int]]:
    workers = max(1, min(workers, total_size))
    segment_size = math.ceil(total_size / workers)
    segments: list[tuple[int, int]] = []
    start = 0
    while start < total_size:
        end = min(start + segment_size - 1, total_size - 1)
        segments.append((start, end))
        start = end + 1
    return segments


async def retry_async(
    action: Callable[[], Awaitable[httpx.Response]],
    retry_policy: RetryPolicy,
) -> httpx.Response:
    attempt = 1
    while True:
        try:
            response = await action()
            if response.status_code >= 500:
                await response.aclose()
                raise httpx.HTTPStatusError(
                    message=f"Server error: {response.status_code}",
                    request=response.request,
                    response=response,
                )
            return response
        except Exception:
            if attempt >= retry_policy.max_attempts:
                raise
            sleep_seconds = min(
                retry_policy.backoff_seconds * (2 ** (attempt - 1)),
                retry_policy.max_backoff_seconds,
            )
            await asyncio.sleep(sleep_seconds)
            attempt += 1


async def detect_range_support(
    client: httpx.AsyncClient,
    url: str,
    retry_policy: RetryPolicy,
    headers: dict[str, str] | None = None,
) -> RangeSupport:
    request_headers = dict(headers or {})

    async def head_action() -> httpx.Response:
        return await client.head(url, headers=request_headers)

    try:
        head_response = await retry_async(head_action, retry_policy)
    except Exception:
        head_response = None

    content_length: int | None = None
    if head_response is not None:
        if head_response.is_success:
            length_header = head_response.headers.get("Content-Length")
            if length_header and length_header.isdigit():
                content_length = int(length_header)
            if head_response.headers.get("Accept-Ranges", "").lower() == "bytes" and content_length and content_length > 0:
                return RangeSupport(supported=True, content_length=content_length)
        await head_response.aclose()

    probe_headers = dict(request_headers)
    probe_headers["Range"] = "bytes=0-0"

    async def probe_action() -> httpx.Response:
        return await client.get(url, headers=probe_headers)

    probe_response = await retry_async(probe_action, retry_policy)
    if probe_response.status_code == 206:
        content_range = probe_response.headers.get("Content-Range", "")
        if "/" in content_range:
            total_str = content_range.split("/")[-1]
            if total_str.isdigit():
                content_length = int(total_str)
        await probe_response.aclose()
        return RangeSupport(supported=True, content_length=content_length)

    length_header = probe_response.headers.get("Content-Length")
    if length_header and length_header.isdigit() and content_length is None:
        content_length = int(length_header)
    await probe_response.aclose()

    return RangeSupport(supported=False, content_length=content_length)
