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

import httpx
import pandas as pd
from geopandas import GeoDataFrame
from shapely import Point

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

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)


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

        self.account_fixture = load_fixture("account_info_1")
        self.address_fixture = load_fixture("coder_address_1")
        self.isolines_fixture = load_fixture("spatial_isolines_1")
        self.routes_fixture = load_fixture("spatial_routes_1")
        self.poi_fixture = load_fixture("coder_poi_1")

    @patch("httpx.Client.request", autospec=True)
    def test_address_batch(self, mock_req):
        # Set up account info mock and individual geocode mocks.
        mock_req.side_effect = [
            httpx.Response(**self.account_fixture["response"]),
            httpx.Response(**self.address_fixture["response"]),
            httpx.Response(**self.address_fixture["response"]),
            httpx.Response(**self.address_fixture["response"]),
        ]

        cgc = CasaGeoCoder(self.client)
        address_list = pd.DataFrame([
            {"id": 1, "address": "Fraunhoferstr. 3, 25524 Itzehoe DEU"},
            {"id": 2, "address": "Hachmannplatz 16, 20099 Hamburg DEU"},
            {"id": 3, "address": "Sophienblatt 25, 24114 Kiel DEU"},
        ])

        result = cgc.address_batch(address_list, address_details=True, coordinates=True)

        self.assertIsInstance(result, GeoDataFrame)
        self.assertEqual(len(result), 3)
        self.assertIn("address", result.columns)
        self.assertIn("position", result.columns)
        self.assertIn("postaladdress", result.columns)
        self.assertSequenceEqual(result["id"].to_list(), [1, 2, 3])

    @patch("httpx.Client.request", autospec=True)
    def test_routes_batch(self, mock_req):
        # Set up account info mock and individual route mocks.
        mock_req.side_effect = [
            httpx.Response(**self.account_fixture["response"]),
            httpx.Response(**self.routes_fixture["response"]),
            httpx.Response(**self.routes_fixture["response"]),
            httpx.Response(**self.routes_fixture["response"]),
        ]

        cgs = CasaGeoSpatial(self.client)
        list_routing = pd.DataFrame([
            {
                "id": 1,
                "origin": POINT_IZET,
                "destination": POINT_HH,
                # "transport_mode": "car",
            },
            {
                "id": 2,
                "origin": POINT_IZET,
                "destination": POINT_HH,
                "transport_mode": "car",
            },
            {
                "id": 3,
                "origin": POINT_IZET,
                "destination": POINT_HH,
                "transport_mode": "car",
            },
        ])

        result = cgs.routes_batch(list_routing, departure_info=True, arrival_info=True)

        self.assertIsInstance(result, GeoDataFrame)
        self.assertEqual(len(result), 3)
        self.assertIn("geometry", result.columns)
        self.assertIn("departure_time", result.columns)
        self.assertIn("arrival_time", result.columns)
        self.assertSequenceEqual(result["id"].to_list(), [1, 2, 3])

    @patch("httpx.Client.request", autospec=True)
    def test_isolines_batch(self, mock_req):
        # Set up account info mock and individual isoline mocks.
        mock_req.side_effect = [
            httpx.Response(**self.account_fixture["response"]),
            httpx.Response(**self.isolines_fixture["response"]),
            httpx.Response(**self.isolines_fixture["response"]),
            httpx.Response(**self.isolines_fixture["response"]),
        ]

        cgs = CasaGeoSpatial(self.client)
        list_isolines = pd.DataFrame([
            {
                "id": 1,
                "position": POINT_HH,
                "range_type": "time",
                "ranges": [3, 9, 15],
                "transport_mode": "car",
            },
            {
                "id": 2,
                "position": POINT_HH,
                "range_type": "time",
                "ranges": [3, 9, 15],
                "transport_mode": "car",
            },
            {
                "id": 3,
                "position": POINT_HH,
                "range_type": "time",
                "ranges": [3, 9, 15],
                "transport_mode": "car",
            },
        ])

        result = cgs.isolines_batch(list_isolines, departure_info=True)

        self.assertIsInstance(result, GeoDataFrame)
        # spatial_isolines_1 fixture has 3 isolines per request, so 3 requests * 3 = 9 rows
        self.assertEqual(len(result), 9)
        self.assertIn("geometry", result.columns)
        self.assertIn("departure_time", result.columns)
        self.assertSequenceEqual(
            list(zip(result["id"], result["subid"], strict=True)),
            [(1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2)],
        )

    @patch("httpx.Client.request", autospec=True)
    def test_poi_batch(self, mock_req):
        # Set up account info mock and individual poi mocks.
        mock_req.side_effect = [
            httpx.Response(**self.account_fixture["response"]),
            httpx.Response(**self.poi_fixture["response"]),
            httpx.Response(**self.poi_fixture["response"]),
            httpx.Response(**self.poi_fixture["response"]),
        ]

        cgc = CasaGeoCoder(self.client)
        list_poi = pd.DataFrame([
            {
                "id": 1,
                "position": POINT_IZET,
                "categories": ["100"],
            },
            {
                "id": 2,
                "position": POINT_IZET,
                "categories": ["100"],
            },
            {
                "id": 3,
                "position": POINT_IZET,
                "categories": ["100"],
            },
        ])

        result = cgc.poi_batch(list_poi, address_details=True, coordinates=True)

        self.assertIsInstance(result, GeoDataFrame)
        # coder_poi_1 fixture has 20 items in response
        self.assertEqual(len(result), 3 * 20)
        self.assertIn("title", result.columns)
        self.assertIn("position", result.columns)
        self.assertSequenceEqual(
            list(zip(result["id"], result["subid"], strict=True)),
            [(i, j) for i in range(1, 4) for j in range(20)],
        )


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