import asyncio
import os
from typing import Literal
import warnings
from collections.abc import Callable
from http import HTTPStatus

import httpx

from .config import MAX_DELAY
from .models import Destination, JourneyResult
from .rate_limiter import RateLimiter


BASE_URL = "https://api.tfl.gov.uk"


async def fetch_journey_for_mode(
    client: httpx.AsyncClient,
    rate_limiter: RateLimiter,
    from_location: str,
    to_location: str,
    journey_date: str,
    journey_time: str,
    journey_type: Literal["quick"] | Literal["easy"] | Literal["cycle"],
    retry_count: int = 5,
) -> int | None:
    """Fetch journey time for a specific mode with rate limiting."""
    backoff = 1.0
    for attempt in range(retry_count):
        try:
            await rate_limiter.acquire()

            journey_preference = {
                "quick": "LeastTime",
                "easy": "LeastInterchange",
                "cycle": None,
            }[journey_type]

            cycle_preference = {
                "quick": None,
                "easy": None,
                "cycle": "AllTheWay",
            }[journey_type]

            # curl -s "https://api.tfl.gov.uk/Journey/Meta/Modes" | jq '.[].modeName'
            mode = {
                "quick": [
                    "bus",
                    "overground",
                    "national-rail",
                    "international-rail",
                    "elizabeth-line",
                    "tube",
                    "coach",
                    "dlr",
                    "cable-car",
                    "replacement-bus",
                    "tram",
                    "river-bus",
                    "walking",
                    "cycle",
                ],
                "easy": [
                    "bus",
                    "overground",
                    "national-rail",
                    "international-rail",
                    "elizabeth-line",
                    "replacement-bus",
                    "tube",
                    "coach",
                    "dlr",
                    "cable-car",
                    "tram",
                    "river-bus",
                ],
                "cycle": ["cycle"],
            }[journey_type]

            params: dict = {
                "date": journey_date,
                "time": journey_time,
                "nationalSearch": "true",
                "timeIs": "Arriving",
                "cyclePreference": cycle_preference,
                "bikeProficiency": "Fast",
                "walkingOptimization": str(journey_type == "quick").lower(),
                "mode": ",".join(mode),
            }
            if journey_preference:
                params["journeyPreference"] = journey_preference

            url = f"/Journey/JourneyResults/{from_location}/to/{to_location}"
            response = await client.get(url, params=params)

            if response.status_code == HTTPStatus.OK:
                data = response.json()
                journeys = data.get("journeys", [])
                if journeys:
                    durations = [
                        j["duration"] for j in journeys if j.get("duration") is not None
                    ]
                    if durations:
                        return min(durations)
                return None
            elif response.status_code in (
                HTTPStatus.TOO_MANY_REQUESTS,
                HTTPStatus.INTERNAL_SERVER_ERROR,
                HTTPStatus.BAD_GATEWAY,
                HTTPStatus.SERVICE_UNAVAILABLE,
                HTTPStatus.GATEWAY_TIMEOUT,
            ):
                warnings.warn(
                    f"HTTP {response.status_code} for {journey_type} from {from_location}, "
                    f"retrying in {backoff:.1f}s (attempt {attempt + 1}/{retry_count})",
                    stacklevel=2,
                )
                await asyncio.sleep(backoff)
                backoff = min(backoff * 2, MAX_DELAY)
                continue
            else:
                return None
        except Exception as e:
            warnings.warn(
                f"Network error for {journey_type} from {from_location}: {e}, "
                f"retrying in {backoff:.1f}s (attempt {attempt + 1}/{retry_count})",
                stacklevel=2,
            )
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, MAX_DELAY)
            continue
    warnings.warn(
        f"Failed to fetch {journey_type} from {from_location} after {retry_count} attempts",
        stacklevel=2,
    )
    return None


async def fetch_all_modes(
    client: httpx.AsyncClient,
    rate_limiter: RateLimiter,
    postcode: str,
    lat: float,
    lon: float,
    to_location: str,
    journey_date: str,
    journey_time: str,
    semaphore: asyncio.Semaphore,
) -> JourneyResult:
    """Fetch journey times for all transport modes using coordinates."""
    async with semaphore:
        try:
            from_location = f"{lat},{lon}"

            easy = await fetch_journey_for_mode(
                client,
                rate_limiter,
                from_location,
                to_location,
                journey_date,
                journey_time,
                journey_type="easy",
            )
            quick = await fetch_journey_for_mode(
                client,
                rate_limiter,
                from_location,
                to_location,
                journey_date,
                journey_time,
                journey_type="quick",
            )
            cycling = await fetch_journey_for_mode(
                client,
                rate_limiter,
                from_location,
                to_location,
                journey_date,
                journey_time,
                journey_type="cycle",
            )

            return JourneyResult(
                postcode=postcode,
                public_transport_easy_minutes=easy,
                public_transport_quick_minutes=quick,
                cycling_minutes=cycling,
            )
        except Exception as e:
            print(f"Error: {e}")
            return JourneyResult(postcode=postcode, error=str(e))


async def fetch_journey_times(
    postcode_data: list[tuple[str, float, float]],
    dest: Destination,
    journey_date: str,
    journey_time: str,
    max_concurrent: int = 2,
    progress_callback: Callable[[JourneyResult], None] | None = None,
) -> list[JourneyResult]:
    """Fetch journey times for all postcodes with rate limiting.

    Args:
        postcode_data: List of (postcode, lat, lon) tuples
        dest: Destination for journey planning
        journey_date: Date in YYYYMMDD format
        journey_time: Time in HHMM format
        max_concurrent: Maximum concurrent API requests
        progress_callback: Optional callback called with each result

    Returns:
        List of JourneyResult objects in the same order as postcode_data
    """
    semaphore = asyncio.Semaphore(max_concurrent)
    to_location = dest.to_tfl_location()
    rate_limiter = RateLimiter()

    # TFL API authentication via app_key query parameter
    tfl_token = os.environ.get("TFL_TOKEN")
    if not tfl_token:
        raise RuntimeError("TFL_TOKEN environment variable not set")
    params = {"app_key": tfl_token}

    async with httpx.AsyncClient(
        base_url=BASE_URL,
        params=params,
        timeout=httpx.Timeout(30),
    ) as client:
        tasks = [
            fetch_all_modes(
                client,
                rate_limiter,
                pc,
                lat,
                lon,
                to_location,
                journey_date,
                journey_time,
                semaphore,
            )
            for pc, lat, lon in postcode_data
        ]

        results = []
        for coro in asyncio.as_completed(tasks):
            result = await coro
            results.append(result)
            if progress_callback:
                progress_callback(result)

        postcode_to_result = {r.postcode: r for r in results}
        return [postcode_to_result[pc] for pc, _, _ in postcode_data]
