import json
from collections.abc import Iterable
from enum import StrEnum
from typing import Any, Callable
from urllib.parse import urljoin

import urllib3

from ors.exceptions import FailedRequest, HTTPError
from ors.types import Context, GeoJSON, HTTPClient


def _prepare_headers(headers: dict[str, Any]) -> dict[str, Any]:
    return {k: v for k, v in headers.items() if v is not None}


class Endpoints(StrEnum):
    ISOCHRONES = "isochrones"


def _prepare_isoch_url(base: str, endpoint: str, profile: str) -> str:
    ep = endpoint + "/" + profile
    return urljoin(base=base, url=ep)


def _parse_isoch_response(resp: str) -> GeoJSON:
    return json.loads(resp)


def _prepare_isoch_body(body: dict[str, Any]) -> str:
    return json.dumps({k: v for k, v in body.items() if v is not None})


def isochrones(
    locations: Iterable[tuple[float, float]],
    range_: Iterable[int],
    attributes: Iterable[str] | None = None,
    id_: str | None = None,
    intersections: bool | None = None,
    interval: int | None = None,
    location_type: str | None = None,
    options: dict[Any, Any] | None = None,
    range_type: str | None = None,
    smoothing: int | None = None,
    area_units: str | None = None,
    units: str | None = None,
    profile: str = "driving-car",
) -> Callable[[HTTPClient, Context], GeoJSON]:
    body = {
        "locations": locations,
        "range": range_,
        "attributes": attributes,
        "id": id_,
        "intersections": intersections,
        "interval": interval,
        "location_type": location_type,
        "options": options,
        "range_type": range_type,
        "smoothing": smoothing,
        "area_units": area_units,
        "units": units,
    }

    def call(http: HTTPClient, ctx: Context) -> GeoJSON:
        url = _prepare_isoch_url(ctx.base_url, Endpoints.ISOCHRONES, profile)
        try:
            resp = http.request(
                "POST",
                url,
                body=_prepare_isoch_body(body),
                headers=_prepare_headers(ctx.headers),
            )
        except urllib3.exceptions.HTTPError as e:
            raise HTTPError(f"request failed on isochrones endpoint: {e}")
        if resp.status != 200:
            raise FailedRequest("request returned a failed status")
        return _parse_isoch_response(resp.data)

    return call
