"""Tests for python_pkg.geo_data._warsaw_places module."""

from __future__ import annotations

from unittest.mock import MagicMock, patch

import geopandas as gpd
from shapely.geometry import LineString

from python_pkg.geo_data._warsaw_places import (
    _filter_streets_by_length,
    get_warsaw_landmarks,
    get_warsaw_streets,
)


class TestGetWarsawStreets:
    """Tests for get_warsaw_streets."""

    @patch("python_pkg.geo_data._warsaw_places._filter_streets_by_length")
    @patch("python_pkg.geo_data._warsaw_places.gpd.read_file")
    @patch("python_pkg.geo_data._warsaw_places.CACHE_DIR")
    def test_cached(
        self,
        mock_cache_dir: MagicMock,
        mock_read: MagicMock,
        mock_filter: MagicMock,
    ) -> None:
        mock_path = MagicMock()
        mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path)
        mock_path.exists.return_value = True

        mock_gdf = MagicMock(spec=gpd.GeoDataFrame)
        mock_read.return_value = mock_gdf
        mock_filter.return_value = mock_gdf

        result = get_warsaw_streets()
        assert result is mock_gdf

    def test_downloads(self) -> None:
        with (
            patch("python_pkg.geo_data._warsaw_places.sys.stdout"),
            patch("python_pkg.geo_data._warsaw_places.CACHE_DIR") as mock_cache_dir,
            patch("python_pkg.geo_data._warsaw_places._overpass_query") as mock_query,
            patch("python_pkg.geo_data._warsaw_places._ensure_cache_dir"),
            patch(
                "python_pkg.geo_data._warsaw_places.gpd.GeoDataFrame.from_features"
            ) as mock_from_features,
            patch(
                "python_pkg.geo_data._warsaw_places._filter_streets_by_length"
            ) as mock_filter,
        ):
            mock_path = MagicMock()
            mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path)
            mock_path.exists.return_value = False

            mock_query.return_value = {
                "elements": [
                    {
                        "type": "way",
                        "tags": {"name": "Marszałkowska", "highway": "primary"},
                        "geometry": [
                            {"lon": 21.0, "lat": 52.2},
                            {"lon": 21.0, "lat": 52.3},
                        ],
                    },
                    # Too few coords
                    {
                        "type": "way",
                        "tags": {"name": "Short"},
                        "geometry": [{"lon": 21.0, "lat": 52.2}],
                    },
                    # Not a way
                    {"type": "node", "tags": {"name": "Node"}},
                    # Way without geometry
                    {"type": "way", "tags": {"name": "NoGeom"}},
                ]
            }

            mock_gdf = MagicMock(spec=gpd.GeoDataFrame)
            mock_from_features.return_value = mock_gdf
            mock_filter.return_value = mock_gdf

            result = get_warsaw_streets()
            assert result is mock_gdf


class TestFilterStreetsByLength:
    """Tests for _filter_streets_by_length."""

    def test_filters_and_merges(self) -> None:
        gdf = gpd.GeoDataFrame(
            {
                "name": ["Marszałkowska", "Marszałkowska", "Unknown", "Short"],
                "geometry": [
                    LineString([(21.0, 52.2), (21.0, 52.3)]),
                    LineString([(21.0, 52.3), (21.0, 52.4)]),
                    LineString([(21.0, 52.2), (21.0, 52.3)]),
                    LineString([(21.0, 52.2), (21.001, 52.2001)]),
                ],
            },
            crs="EPSG:4326",
        )
        result = _filter_streets_by_length(gdf, 500)
        # Only streets >= 500m should be included
        for _, row in result.iterrows():
            assert row["length_m"] >= 500

    def test_single_segment(self) -> None:
        gdf = gpd.GeoDataFrame(
            {
                "name": ["Marszałkowska"],
                "geometry": [LineString([(21.0, 52.2), (21.0, 52.3)])],
            },
            crs="EPSG:4326",
        )
        result = _filter_streets_by_length(gdf, 0)
        # Single segment should remain a LineString
        assert len(result) == 1

    def test_unknown_name_excluded(self) -> None:
        gdf = gpd.GeoDataFrame(
            {
                "name": ["Unknown"],
                "geometry": [LineString([(21.0, 52.2), (21.0, 52.3)])],
            },
            crs="EPSG:4326",
        )
        result = _filter_streets_by_length(gdf, 0)
        assert len(result) == 0

    def test_empty_name_excluded(self) -> None:
        gdf = gpd.GeoDataFrame(
            {
                "name": [""],
                "geometry": [LineString([(21.0, 52.2), (21.0, 52.3)])],
            },
            crs="EPSG:4326",
        )
        result = _filter_streets_by_length(gdf, 0)
        assert len(result) == 0

    def test_no_name_column(self) -> None:
        gdf = gpd.GeoDataFrame(
            {
                "geometry": [LineString([(21.0, 52.2), (21.0, 52.3)])],
            },
            crs="EPSG:4326",
        )
        result = _filter_streets_by_length(gdf, 0)
        assert len(result) == 0


class TestGetWarsawLandmarks:
    """Tests for get_warsaw_landmarks."""

    @patch("python_pkg.geo_data._warsaw_places.gpd.read_file")
    @patch("python_pkg.geo_data._warsaw_places.CACHE_DIR")
    def test_cached(self, mock_cache_dir: MagicMock, mock_read: MagicMock) -> None:
        mock_path = MagicMock()
        mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path)
        mock_path.exists.return_value = True

        mock_gdf = MagicMock(spec=gpd.GeoDataFrame)
        mock_read.return_value = mock_gdf

        result = get_warsaw_landmarks()
        assert result is mock_gdf

    @patch("python_pkg.geo_data._warsaw_places.gpd.GeoDataFrame.from_features")
    @patch("python_pkg.geo_data._warsaw_places._ensure_cache_dir")
    @patch("python_pkg.geo_data._warsaw_places._overpass_query")
    @patch("python_pkg.geo_data._warsaw_places.CACHE_DIR")
    @patch("python_pkg.geo_data._warsaw_places.sys.stdout")
    def test_downloads(
        self,
        mock_stdout: MagicMock,
        mock_cache_dir: MagicMock,
        mock_query: MagicMock,
        mock_ensure: MagicMock,
        mock_from_features: MagicMock,
    ) -> None:
        mock_path = MagicMock()
        mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path)
        mock_path.exists.return_value = False

        mock_query.return_value = {
            "elements": [
                # Node with tourism
                {
                    "type": "node",
                    "tags": {"name": "Muzeum Chopina", "tourism": "museum"},
                    "lon": 21.0,
                    "lat": 52.2,
                },
                # Way with center
                {
                    "type": "way",
                    "tags": {"name": "Łazienki", "tourism": "attraction"},
                    "center": {"lon": 21.0, "lat": 52.2},
                },
                # Node with historic
                {
                    "type": "node",
                    "tags": {"name": "Kolumna Zygmunta", "historic": "monument"},
                    "lon": 21.0,
                    "lat": 52.2,
                },
                # Node with leisure
                {
                    "type": "node",
                    "tags": {"name": "Park Skaryszewski", "leisure": "park"},
                    "lon": 21.0,
                    "lat": 52.2,
                },
                # Node no tourism/historic/leisure -> "landmark"
                {
                    "type": "node",
                    "tags": {"name": "Generic"},
                    "lon": 21.0,
                    "lat": 52.2,
                },
                # Duplicate
                {
                    "type": "node",
                    "tags": {"name": "Muzeum Chopina", "tourism": "museum"},
                    "lon": 21.0,
                    "lat": 52.2,
                },
                # No name
                {
                    "type": "node",
                    "tags": {"tourism": "museum"},
                    "lon": 21.0,
                    "lat": 52.2,
                },
                # Way without center
                {
                    "type": "way",
                    "tags": {"name": "No Center"},
                },
            ]
        }

        mock_gdf = MagicMock(spec=gpd.GeoDataFrame)
        mock_from_features.return_value = mock_gdf

        result = get_warsaw_landmarks()
        assert result is mock_gdf

    @patch("python_pkg.geo_data._warsaw_places._ensure_cache_dir")
    @patch("python_pkg.geo_data._warsaw_places._overpass_query")
    @patch("python_pkg.geo_data._warsaw_places.CACHE_DIR")
    @patch("python_pkg.geo_data._warsaw_places.sys.stdout")
    def test_empty_result(
        self,
        mock_stdout: MagicMock,
        mock_cache_dir: MagicMock,
        mock_query: MagicMock,
        mock_ensure: MagicMock,
    ) -> None:
        mock_path = MagicMock()
        mock_cache_dir.__truediv__ = MagicMock(return_value=mock_path)
        mock_path.exists.return_value = False

        mock_query.return_value = {"elements": []}

        result = get_warsaw_landmarks()
        assert len(result) == 0
