import functools
import json
import logging
import os
import unittest
from contextlib import contextmanager
from unittest.mock import patch

import httpx
from shapely import MultiLineString, MultiPolygon, Point

from casageo.coder import CasaGeoCoder
from casageo.spatial import CasaGeoSpatial
from casageo.tools import CasaGeoClient, CasaGeoError

API_KEY = "XXXXX-XXXXX-XXXXX-XXXXX"
FIXTURE_DIR = os.path.join(os.path.dirname(__file__), "fixtures")

POINT_IZET = Point(9.4854461, 53.9580118)
POINT_HH = Point(10.008223, 53.553089)

casageo_logger = logging.getLogger("casageo.tools")
casageo_logger.setLevel(logging.ERROR)  # Ignore warnings about caught errors.


@functools.cache
def load_fixture(name: str):
    with open(os.path.join(FIXTURE_DIR, f"{name}.json"), mode="rb") as f:
        return json.load(f)


@contextmanager
def mock_request(client: CasaGeoClient, fixture: dict):
    with patch("httpx.Client.request", autospec=True) as mock:
        mock.return_value = httpx.Response(**fixture["response"])
        yield mock
        mock.assert_called_once_with(
            client._client.get_httpx_client(), **fixture["request"]
        )


class TestAccountInfo(unittest.TestCase):
    def setUp(self):
        self.client = CasaGeoClient(API_KEY)
        self.client.preferred_language = "de,en"

    def test_account_info_success(self):
        with mock_request(self.client, load_fixture("account_info_1")):
            result = self.client.account_info()
        self.assertTrue(result)
        self.assertEqual("testuser", result.username())
        self.assertEqual("Professional", result.accounttype())
        self.assertEqual(43359, result.credits())

    def test_account_info_failure(self):
        with patch("httpx.Client.request", return_value=httpx.Response(500)):
            result = self.client.account_info()

        self.assertFalse(result)
        self.assertIsInstance(result.error(), CasaGeoError)
        self.assertEqual("Bad status code 500: b''", str(result.error()))


class TestCoder(unittest.TestCase):
    def setUp(self):
        self.client = CasaGeoClient(API_KEY)
        self.client.preferred_language = "de,en"

        self.cgc = CasaGeoCoder(self.client)

    def test_address(self):
        with mock_request(self.client, load_fixture("coder_address_1")):
            result = self.cgc.address_single("Fraunhoferstr. 3, 25524 Itzehoe DEU")

        self.assertTrue(result)

        df = result.dataframe(
            address_details=True, coordinates=True, match_quality=True
        )
        self.assertEqual(1, len(df))

        row = df.iloc[0]
        self.assertEqual(
            "Fraunhoferstraße 3, 25524 Itzehoe, Deutschland", row["address"]
        )
        self.assertEqual("houseNumber", row["resulttype"])
        self.assertEqual(Point(9.48572, 53.95815), row["position"])
        self.assertEqual(Point(9.48615, 53.95801), row["navigation"])
        self.assertEqual(1.0, row["relevance"])

        # address_details=True
        self.assertEqual(
            "Fraunhoferstraße 3, 25524 Itzehoe, Deutschland", row["postaladdress"]
        )
        self.assertEqual("Deutschland", row["country"])
        self.assertEqual("DEU", row["countrycode"])
        self.assertEqual("Schleswig-Holstein", row["state"])
        self.assertEqual("SH", row["statecode"])
        self.assertEqual("Steinburg", row["county"])
        self.assertEqual("IZ", row["countycode"])
        self.assertEqual("Itzehoe", row["city"])
        self.assertEqual("Fraunhoferstraße", row["street"])
        self.assertEqual("25524", row["postalcode"])
        self.assertEqual("3", row["housenumber"])

        # coordinates=True
        self.assertEqual(9.48572, row["position_lng"])
        self.assertEqual(53.95815, row["position_lat"])
        self.assertEqual(9.48615, row["navigation_lng"])
        self.assertEqual(53.95801, row["navigation_lat"])

        # match_quality=True
        self.assertEqual(1.0, row["mq_country"])
        self.assertEqual(1.0, row["mq_city"])
        self.assertListEqual([1.0], row["mq_streets"])
        self.assertEqual(1.0, row["mq_housenumber"])
        self.assertEqual(1.0, row["mq_postalcode"])

    def test_poi(self):
        with mock_request(self.client, load_fixture("coder_poi_1")):
            result = self.cgc.poi_single(POINT_IZET, categories=["100"])

        self.assertTrue(result)

        df = result.dataframe(
            address_details=True, coordinates=True, category_codes=True
        )
        self.assertEqual(20, len(df))

        row = df.iloc[0]
        self.assertEqual("Plietsch und Lecker", row["title"])
        self.assertEqual("place", row["resulttype"])
        self.assertEqual(Point(9.49558, 53.95869), row["position"])
        self.assertEqual(Point(9.49557, 53.9588), row["navigation"])
        self.assertEqual(667, row["distance"])

        # address_details=True
        self.assertEqual(
            "Plietsch und Lecker, Emmy-Noether-Straße 2, 25524 Itzehoe, Deutschland",
            row["postaladdress"],
        )
        self.assertEqual("Deutschland", row["country"])
        self.assertEqual("DEU", row["countrycode"])
        self.assertEqual("Schleswig-Holstein", row["state"])
        self.assertEqual("SH", row["statecode"])
        self.assertEqual("Steinburg", row["county"])
        self.assertEqual("IZ", row["countycode"])
        self.assertEqual("Itzehoe", row["city"])
        self.assertEqual("Itzehoe", row["district"])
        self.assertEqual("Emmy-Noether-Straße", row["street"])
        self.assertEqual("25524", row["postalcode"])
        self.assertEqual("2", row["housenumber"])

        # coordinates=True
        self.assertEqual(9.49558, row["position_lng"])
        self.assertEqual(53.95869, row["position_lat"])
        self.assertEqual(9.49557, row["navigation_lng"])
        self.assertEqual(53.9588, row["navigation_lat"])

        # category_codes=True
        self.assertListEqual(
            ["100-1000-0009", "100-1000-0000", "200-2000-0011"], row["categories"]
        )
        self.assertIsNone(row["chains"])
        self.assertListEqual(["800-058"], row["foodtypes"])

        # category_dataframe
        cdf = result.category_dataframe()
        self.assertGreater(len(cdf), 0)
        self.assertEqual(0, cdf.iloc[0]["subid"])
        self.assertEqual("100-1000-0009", cdf.iloc[0]["category_code"])
        self.assertEqual("Fastfood", cdf.iloc[0]["category_name"])
        self.assertTrue(cdf.iloc[0]["category_isprimary"])

        # contact_dataframe
        condf = result.contact_dataframe()
        self.assertGreater(len(condf), 0)
        self.assertEqual(0, condf.iloc[0]["subid"])
        self.assertEqual("phone", condf.iloc[0]["contact_type"])
        self.assertEqual("+4948933739083", condf.iloc[0]["contact_value"])


class TestSpatialClient(unittest.TestCase):
    def setUp(self):
        self.client = CasaGeoClient(API_KEY)
        self.client.preferred_language = "de,en"

        self.cgs = CasaGeoSpatial(self.client)
        self.cgs.transport_mode = "car"
        self.cgs.routing_mode = "fast"

    def test_isolines(self):
        with mock_request(self.client, load_fixture("spatial_isolines_1")):
            result = self.cgs.isolines_single(POINT_HH, [3, 9, 15], range_type="time")

        self.assertTrue(result)

        df = result.dataframe(departure_info=True)
        self.assertEqual(3, len(df))

        row = df.iloc[0]
        self.assertEqual(0, row["subid"])
        self.assertEqual("time", row["rangetype"])
        self.assertEqual(3.0, row["rangevalue"])
        self.assertIsInstance(row["geometry"], MultiPolygon)
        self.assertTrue(row["geometry"].is_valid)

        self.assertEqual(Point(10.008237, 53.5530903), row["departure_position"])
        self.assertEqual(Point(10.008223, 53.553089), row["departure_queryposition"])

    def test_routes(self):
        with mock_request(self.client, load_fixture("spatial_routes_1")):
            result = self.cgs.routes_single(POINT_IZET, POINT_HH)

        self.assertTrue(result)

        df = result.dataframe(departure_info=True, arrival_info=True)
        self.assertEqual(1, len(df))

        row = df.iloc[0]
        self.assertEqual(0, row["subid"])
        self.assertIsInstance(row["geometry"], MultiLineString)
        self.assertTrue(row["geometry"].is_valid)
        self.assertEqual(62614, row["length"])
        self.assertEqual(45.3, row["duration"])

        # departure_info=True
        self.assertEqual(Point(9.4861499, 53.95801), row["departure_position"])
        self.assertEqual(Point(9.485446, 53.9580117), row["departure_queryposition"])

        # arrival_info=True
        self.assertEqual(Point(10.008237, 53.5530903), row["arrival_position"])
        self.assertEqual(Point(10.008223, 53.553089), row["arrival_queryposition"])


if __name__ == "__main__":
    unittest.main()
