import os

import mercantile
import pytest
import requests

import xyzservices.providers as xyz

flat_free = xyz.filter(requires_token=False).flatten()


def check_provider(provider):
    for key in ["attribution", "name"]:
        assert key in provider
    assert provider.url.startswith("http")
    for option in ["{z}", "{y}", "{x}"]:
        assert option in provider.url


def get_tile(provider):
    bounds = provider.get("bounds", [[-180, -90], [180, 90]])
    lat = (bounds[0][0] + bounds[1][0]) / 2
    lon = (bounds[0][1] + bounds[1][1]) / 2
    zoom = (provider.get("min_zoom", 0) + provider.get("max_zoom", 20)) // 2
    tile = mercantile.tile(lon, lat, zoom)
    z = tile.z
    x = tile.x
    y = tile.y
    return (z, x, y)


def get_response(url):
    s = requests.Session()
    a = requests.adapters.HTTPAdapter(max_retries=3)
    s.mount("http://", a)
    s.mount("https://", a)
    try:
        r = s.get(url, timeout=30)
    except requests.ConnectionError:
        pytest.xfail("Timeout.")
    return r.status_code


def get_test_result(provider, allow_403=True):
    if provider.get("status"):
        pytest.xfail("Provider is known to be broken.")

    z, x, y = get_tile(provider)

    try:
        r = get_response(provider.build_url(z=z, x=x, y=y))
        assert r == requests.codes.ok
    except AssertionError:
        if r == 403 and allow_403:
            pytest.xfail("Provider not available due to API restrictions (Error 403).")

        elif r == 503:
            pytest.xfail("Service temporarily unavailable (Error 503).")

        elif r == 502:
            pytest.xfail("Bad Gateway (Error 502).")

        # check another tiles
        elif r == 404:
            # in some cases, the computed tile is not available. trying known tiles.
            options = [
                (12, 2154, 1363),
                (6, 13, 21),
                (16, 33149, 22973),
                (0, 0, 0),
                (2, 6, 7),
                (6, 21, 31),
                (6, 21, 32),
                (6, 21, 33),
                (6, 22, 31),
                (6, 22, 32),
                (6, 22, 33),
                (6, 23, 31),
                (6, 23, 32),
                (6, 23, 33),
                (9, 259, 181),
                (12, 2074, 1410),
            ]
            results = []
            for o in options:
                z, x, y = o
                r = get_response(provider.build_url(z=z, x=x, y=y))
                results.append(r)
            if not any(x == requests.codes.ok for x in results):
                raise ValueError(f"Response code: {r}")
        else:
            raise ValueError(f"Response code: {r}")


@pytest.mark.parametrize("provider_name", xyz.flatten())
def test_minimal_provider_metadata(provider_name):
    provider = xyz.flatten()[provider_name]
    check_provider(provider)


@pytest.mark.request
@pytest.mark.parametrize("name", flat_free)
def test_free_providers(name):
    provider = flat_free[name]
    if "Stadia" in name:
        pytest.skip("Stadia doesn't support tile download in this way.")
    elif "GeoportailFrance" in name:
        try:
            get_test_result(provider)
        except ValueError:
            pytest.xfail("GeoportailFrance API is unstable.")
    else:
        get_test_result(provider)


# test providers requiring API keys. Store API keys in GitHub secrets and load them as
# environment variables in CI Action. Note that env variable is loaded as empty on PRs
# from a fork.


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.Thunderforest)
def test_thunderforest(provider_name):
    try:
        token = os.environ["THUNDERFOREST"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.Thunderforest[provider_name](apikey=token)
    get_test_result(provider, allow_403=False)


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.Jawg)
def test_jawg(provider_name):
    try:
        token = os.environ["JAWG"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.Jawg[provider_name](accessToken=token)
    get_test_result(provider, allow_403=False)


@pytest.mark.request
def test_mapbox():
    try:
        token = os.environ["MAPBOX"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.MapBox(accessToken=token)
    get_test_result(provider, allow_403=False)


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.MapTiler)
def test_maptiler(provider_name):
    try:
        token = os.environ["MAPTILER"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.MapTiler[provider_name](key=token)
    get_test_result(provider, allow_403=False)


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.TomTom)
def test_tomtom(provider_name):
    try:
        token = os.environ["TOMTOM"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.TomTom[provider_name](apikey=token)
    get_test_result(provider, allow_403=False)


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.OpenWeatherMap)
def test_openweathermap(provider_name):
    try:
        token = os.environ["OPENWEATHERMAP"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.OpenWeatherMap[provider_name](apiKey=token)
    get_test_result(provider, allow_403=False)


# HEREV3 seems to block GHA as it errors with E429
# @pytest.mark.request
# @pytest.mark.parametrize("provider_name", xyz.HEREv3)
# def test_herev3(provider_name):
#     try:
#         token = os.environ["HEREV3"]
#     except KeyError:
#         pytest.xfail("Missing API token.")
#     if token == "":
#         pytest.xfail("Token empty.")

#     provider = xyz.HEREv3[provider_name](apiKey=token)
#     get_test_result(provider, allow_403=False)


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.Stadia)
def test_stadia(provider_name):
    try:
        token = os.environ["STADIA"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.Stadia[provider_name](api_key=token)
    provider["url"] = provider["url"] + "?api_key={api_key}"
    get_test_result(provider, allow_403=False)


@pytest.mark.request
@pytest.mark.parametrize("provider_name", xyz.OrdnanceSurvey)
def test_os(provider_name):
    try:
        token = os.environ["ORDNANCESURVEY"]
    except KeyError:
        pytest.xfail("Missing API token.")
    if token == "":
        pytest.xfail("Token empty.")

    provider = xyz.OrdnanceSurvey[provider_name](key=token)
    get_test_result(provider, allow_403=False)


# NOTE: AzureMaps are not tested as their free account is limited to
# 5000 downloads (total, not per month)
