"""Base test class and utilities for all grid systems."""

import json
import tempfile
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Any
from unittest.mock import patch

import pytest

from earthcatalog.grid_systems import (
    GeoJSONGridSystem,
    GridSystem,
    H3GridSystem,
    ITSLiveGridSystem,
    MGRSGridSystem,
    S2GridSystem,
    SimpleLatLonGrid,
    UTMGridSystem,
    get_grid_system,
)


class BaseGridSystemTest(ABC):
    """Base test class for all grid systems."""

    @abstractmethod
    def get_grid_system(self) -> GridSystem:
        """Return an instance of the grid system to test."""
        pass

    @abstractmethod
    def get_test_point_in_tile(self) -> dict[str, Any]:
        """Return a point geometry that should intersect with a tile."""
        pass

    @abstractmethod
    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        """Return a point geometry outside the grid's normal coverage."""
        pass

    @abstractmethod
    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        """Return a polygon that should span multiple tiles."""
        pass

    @abstractmethod
    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        """Return a polygon contained in a single tile."""
        pass

    def test_grid_initialization(self):
        """Test grid system initialization."""
        grid = self.get_grid_system()
        assert grid is not None
        assert hasattr(grid, "tiles_for_geometry")
        assert hasattr(grid, "tiles_for_geometry_with_spanning_detection")
        assert hasattr(grid, "get_global_partition_threshold")

    def test_point_in_tile(self):
        """Test point geometry intersection."""
        grid = self.get_grid_system()
        point_geom = self.get_test_point_in_tile()

        tiles = grid.tiles_for_geometry(point_geom)
        assert len(tiles) >= 1
        assert all(isinstance(tile, str) for tile in tiles)

    def test_point_with_spanning_detection(self):
        """Test point geometry with spanning detection."""
        grid = self.get_grid_system()
        point_geom = self.get_test_point_in_tile()

        tiles, is_spanning = grid.tiles_for_geometry_with_spanning_detection(point_geom)
        assert len(tiles) >= 1
        assert is_spanning is False  # Points should never span multiple tiles

    def test_polygon_spanning_multiple_tiles(self):
        """Test polygon spanning multiple tiles."""
        grid = self.get_grid_system()
        polygon_geom = self.get_test_polygon_spanning_multiple()

        tiles, is_spanning = grid.tiles_for_geometry_with_spanning_detection(polygon_geom)
        assert len(tiles) >= 1
        # Note: Some grid systems might return single tile for small polygons

    def test_polygon_single_tile(self):
        """Test polygon contained in single tile."""
        grid = self.get_grid_system()
        polygon_geom = self.get_test_polygon_single_tile()

        tiles, is_spanning = grid.tiles_for_geometry_with_spanning_detection(polygon_geom)
        assert len(tiles) >= 1
        # Most grid systems should return single tile for small polygons

    def test_global_partition_threshold(self):
        """Test global partition threshold method."""
        grid = self.get_grid_system()

        # Test default threshold
        threshold = grid.get_global_partition_threshold(100)
        assert threshold == 100

        # Test with custom thresholds (should return default for most grids)
        custom_thresholds = {"h3": {6: 10}}
        threshold = grid.get_global_partition_threshold(50, custom_thresholds)
        assert isinstance(threshold, int)

    def test_tiles_for_geometry_consistency(self):
        """Test that tiles_for_geometry and tiles_for_geometry_with_spanning_detection are consistent."""
        grid = self.get_grid_system()
        point_geom = self.get_test_point_in_tile()

        tiles_simple = grid.tiles_for_geometry(point_geom)
        tiles_with_spanning, _ = grid.tiles_for_geometry_with_spanning_detection(point_geom)

        assert set(tiles_simple) == set(tiles_with_spanning)


class TestH3GridSystem(BaseGridSystemTest):
    """Test H3 grid system."""

    def get_grid_system(self) -> GridSystem:
        return H3GridSystem(resolution=6)

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [-122.4194, 37.7749],  # San Francisco
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Null Island
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        # Large polygon covering multiple H3 cells
        return {
            "type": "Polygon",
            "coordinates": [[[-123.0, 37.0], [-121.0, 37.0], [-121.0, 38.0], [-123.0, 38.0], [-123.0, 37.0]]],
        }

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        # Small polygon within single H3 cell
        return {
            "type": "Polygon",
            "coordinates": [[[-122.42, 37.77], [-122.41, 37.77], [-122.41, 37.78], [-122.42, 37.78], [-122.42, 37.77]]],
        }

    def test_h3_specific_features(self):
        """Test H3-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, H3GridSystem)
        assert grid.resolution == 6

        # Test that different resolutions work
        fine_grid = H3GridSystem(resolution=8)
        assert fine_grid.resolution == 8

    def test_h3_import_error(self):
        """Test H3 import error handling."""
        # Mock the import at the module level where it's used
        with patch("earthcatalog.grid_systems.h3", None):
            with pytest.raises(ImportError, match="h3 required"):
                H3GridSystem(resolution=6)


class TestS2GridSystem(BaseGridSystemTest):
    """Test S2 grid system."""

    def get_grid_system(self) -> GridSystem:
        return S2GridSystem(resolution=13)

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [-122.4194, 37.7749],  # San Francisco
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Null Island
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        return {
            "type": "Polygon",
            "coordinates": [[[-123.0, 37.0], [-121.0, 37.0], [-121.0, 38.0], [-123.0, 38.0], [-123.0, 37.0]]],
        }

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        return {
            "type": "Polygon",
            "coordinates": [[[-122.42, 37.77], [-122.41, 37.77], [-122.41, 37.78], [-122.42, 37.78], [-122.42, 37.77]]],
        }

    def test_s2_specific_features(self):
        """Test S2-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, S2GridSystem)
        assert grid.resolution == 13

        # Test that different resolutions work
        fine_grid = S2GridSystem(resolution=15)
        assert fine_grid.resolution == 15

    @pytest.mark.skipif(
        not pytest.importorskip("s2sphere", reason="s2sphere not available"), reason="s2sphere library required"
    )
    def test_s2_import_error(self):
        """Test S2 import error handling."""
        with patch.dict("sys.modules", {"s2sphere": None}):
            with pytest.raises(ImportError, match="s2sphere required"):
                S2GridSystem(resolution=13)


class TestMGRSGridSystem(BaseGridSystemTest):
    """Test MGRS grid system."""

    def get_grid_system(self) -> GridSystem:
        return MGRSGridSystem(resolution=5)

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [-122.4194, 37.7749],  # San Francisco
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Null Island
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        return {
            "type": "Polygon",
            "coordinates": [[[-123.0, 37.0], [-121.0, 37.0], [-121.0, 38.0], [-123.0, 38.0], [-123.0, 37.0]]],
        }

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        return {
            "type": "Polygon",
            "coordinates": [[[-122.42, 37.77], [-122.41, 37.77], [-122.41, 37.78], [-122.42, 37.78], [-122.42, 37.77]]],
        }

    def test_mgrs_specific_features(self):
        """Test MGRS-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, MGRSGridSystem)
        assert grid.resolution == 5

        # Test that different valid precisions work (1-5)
        low_precision_grid = MGRSGridSystem(resolution=1)
        assert low_precision_grid.resolution == 1

        # Test that invalid resolution raises ValueError
        with pytest.raises(ValueError, match="MGRS resolution must be 1-5"):
            MGRSGridSystem(resolution=6)
        with pytest.raises(ValueError, match="MGRS resolution must be 1-5"):
            MGRSGridSystem(resolution=0)

    @pytest.mark.skipif(not pytest.importorskip("mgrs", reason="mgrs not available"), reason="mgrs library required")
    def test_mgrs_import_error(self):
        """Test MGRS import error handling."""
        with patch.dict("sys.modules", {"mgrs": None}):
            with pytest.raises(ImportError, match="mgrs required"):
                MGRSGridSystem(resolution=5)


class TestUTMGridSystem(BaseGridSystemTest):
    """Test UTM grid system."""

    def get_grid_system(self) -> GridSystem:
        return UTMGridSystem(resolution=1)

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [-122.4194, 37.7749],  # San Francisco (Zone 10N)
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Null Island (Zone 31N)
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        # Polygon spanning multiple UTM zones
        return {
            "type": "Polygon",
            "coordinates": [
                [
                    [-12.0, 0.0],  # Zone 1N
                    [12.0, 0.0],  # Zone 33N
                    [12.0, 10.0],
                    [-12.0, 10.0],
                    [-12.0, 0.0],
                ]
            ],
        }

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        return {
            "type": "Polygon",
            "coordinates": [[[-122.5, 37.5], [-122.0, 37.5], [-122.0, 38.0], [-122.5, 38.0], [-122.5, 37.5]]],
        }

    def test_utm_specific_features(self):
        """Test UTM-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, UTMGridSystem)
        assert grid.resolution == 1  # UTM doesn't really use resolution

        # Test different zones
        point_pacific = {"type": "Point", "coordinates": [-170.0, 0.0]}
        tiles = grid.tiles_for_geometry(point_pacific)
        assert len(tiles) == 1

        point_europe = {"type": "Point", "coordinates": [10.0, 50.0]}
        tiles = grid.tiles_for_geometry(point_europe)
        assert len(tiles) == 1


class TestSimpleLatLonGrid(BaseGridSystemTest):
    """Test Simple Lat/Lon grid system."""

    def get_grid_system(self) -> GridSystem:
        return SimpleLatLonGrid(resolution=1)

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [-122.4, 37.7],  # Should be in lat+37_lon-123
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Should be in lat+0_lon+0
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        # Polygon spanning multiple degree cells
        return {
            "type": "Polygon",
            "coordinates": [[[-122.5, 37.5], [-120.5, 37.5], [-120.5, 38.5], [-122.5, 38.5], [-122.5, 37.5]]],
        }

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        return {
            "type": "Polygon",
            "coordinates": [[[-122.1, 37.1], [-122.0, 37.1], [-122.0, 37.2], [-122.1, 37.2], [-122.1, 37.1]]],
        }

    def test_latlon_specific_features(self):
        """Test Lat/Lon-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, SimpleLatLonGrid)
        assert grid.resolution == 1

        # Test different resolutions
        fine_grid = SimpleLatLonGrid(resolution=1)  # Use int to match type hint
        assert fine_grid.resolution == 1

        # Test tile naming convention
        # For a point at (-122.4, 37.7), with floor division:
        # floor(37.7/1)*1 = 37, floor(-122.4/1)*1 = -123
        # So the cell is lat+037_lon-123 (cell spans -123 to -122 for lon)
        point = {"type": "Point", "coordinates": [-122.4, 37.7]}
        tiles = grid.tiles_for_geometry(point)
        assert len(tiles) == 1
        assert tiles[0].startswith("lat+037_")  # Padded format
        assert tiles[0].endswith("_lon-123")  # floor(-122.4) = -123


class TestITSLiveGridSystem(BaseGridSystemTest):
    """Test ITS_LIVE grid system."""

    def get_grid_system(self) -> GridSystem:
        return ITSLiveGridSystem()

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [-40.0, 60.0],  # Should be in N60W040
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Should be in N00E000
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        # Polygon spanning multiple 10-degree cells
        return {
            "type": "Polygon",
            "coordinates": [[[-45.0, 55.0], [-25.0, 55.0], [-25.0, 65.0], [-45.0, 65.0], [-45.0, 55.0]]],
        }

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        # Small polygon within single ITS_LIVE cell
        return {
            "type": "Polygon",
            "coordinates": [[[-42.0, 58.0], [-38.0, 58.0], [-38.0, 62.0], [-42.0, 62.0], [-42.0, 58.0]]],
        }

    def test_itslive_specific_features(self):
        """Test ITS_LIVE-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, ITSLiveGridSystem)
        assert grid.resolution == 10

        # Test specific naming convention
        point = {"type": "Point", "coordinates": [-40.0, 60.0]}
        tiles = grid.tiles_for_geometry(point)
        assert len(tiles) == 1
        assert tiles[0] == "N60W040"

        # Test different quadrants
        test_cases = [
            ({"type": "Point", "coordinates": [40.0, 60.0]}, "N60E040"),  # NE quadrant
            ({"type": "Point", "coordinates": [-40.0, 60.0]}, "N60W040"),  # NW quadrant
            ({"type": "Point", "coordinates": [40.0, -60.0]}, "S60E040"),  # SE quadrant
            ({"type": "Point", "coordinates": [-40.0, -60.0]}, "S60W040"),  # SW quadrant
            ({"type": "Point", "coordinates": [0.0, 0.0]}, "N00E000"),  # Origin (center of cell)
        ]

        for point_geom, expected_tile in test_cases:
            tiles = grid.tiles_for_geometry(point_geom)
            assert len(tiles) == 1
            assert tiles[0] == expected_tile, (
                f"Expected {expected_tile}, got {tiles[0]} for point {point_geom['coordinates']}"
            )

    def test_itslive_center_based_logic(self):
        """Test that ITS_LIVE uses center-based grid cells."""
        grid = self.get_grid_system()

        # Test points at different positions within a cell
        # All these points should map to the same cell (N60W040)
        test_points = [
            [-40.0, 60.0],  # Center
            [-36.0, 56.0],  # Bottom-right corner of cell
            [-44.0, 64.0],  # Top-left corner of cell
            [-42.0, 58.0],  # Various other points in cell
            [-38.0, 62.0],
        ]

        for lon, lat in test_points:
            point = {"type": "Point", "coordinates": [lon, lat]}
            tiles = grid.tiles_for_geometry(point)
            assert len(tiles) == 1
            assert tiles[0] == "N60W040", f"Point [{lon}, {lat}] should be in N60W040, got {tiles[0]}"

    def test_itslive_polygon_intersection(self):
        """Test polygon intersection logic matches expected behavior."""
        grid = self.get_grid_system()

        # Large polygon that should intersect multiple cells
        large_polygon = {
            "type": "Polygon",
            "coordinates": [[[-75.0, 70.0], [-25.0, 70.0], [-25.0, 80.0], [-75.0, 80.0], [-75.0, 70.0]]],
        }

        tiles, is_spanning = grid.tiles_for_geometry_with_spanning_detection(large_polygon)
        assert len(tiles) > 1
        assert is_spanning is True

        # Verify correct tiles are included (center-based 10x10 degree logic)
        # Polygon spans lon -75 to -25 (centers at -70, -60, -50, -40, -30)
        # and lat 70 to 80 (centers at 70, 80)
        expected_tiles = {
            "N70W070",
            "N70W060",
            "N70W050",
            "N70W040",
            "N70W030",
            "N80W070",
            "N80W060",
            "N80W050",
            "N80W040",
            "N80W030",
        }
        actual_tiles = set(tiles)

        # Should match exactly for this clean polygon
        assert actual_tiles == expected_tiles, (
            f"Expected exact match. Expected: {expected_tiles}, Actual: {actual_tiles}"
        )


class TestGeoJSONGridSystem(BaseGridSystemTest):
    """Test GeoJSON grid system."""

    def setup_method(self):
        """Set up test fixtures."""
        self.temp_dir = tempfile.mkdtemp()

        # Sample GeoJSON data for testing
        self.sample_geojson = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "properties": {"id": "tile_001"},
                    "geometry": {
                        "type": "Polygon",
                        "coordinates": [[[-10.0, -10.0], [10.0, -10.0], [10.0, 10.0], [-10.0, 10.0], [-10.0, -10.0]]],
                    },
                },
                {
                    "type": "Feature",
                    "properties": {"id": "tile_002"},
                    "geometry": {
                        "type": "Polygon",
                        "coordinates": [[[10.0, -10.0], [30.0, -10.0], [30.0, 10.0], [10.0, 10.0], [10.0, -10.0]]],
                    },
                },
            ],
        }

        # Create temporary GeoJSON file
        self.geojson_path = Path(self.temp_dir) / "test_tiles.geojson"
        with open(self.geojson_path, "w") as f:
            json.dump(self.sample_geojson, f)

    def teardown_method(self):
        """Clean up test fixtures."""
        import shutil

        shutil.rmtree(self.temp_dir, ignore_errors=True)

    def get_grid_system(self) -> GridSystem:
        return GeoJSONGridSystem(str(self.geojson_path))

    def get_test_point_in_tile(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [0.0, 0.0],  # Should be in tile_001
        }

    def get_test_point_outside_coverage(self) -> dict[str, Any]:
        return {
            "type": "Point",
            "coordinates": [100.0, 100.0],  # Far outside all tiles
        }

    def get_test_polygon_spanning_multiple(self) -> dict[str, Any]:
        return {"type": "Polygon", "coordinates": [[[5.0, -5.0], [25.0, -5.0], [25.0, 5.0], [5.0, 5.0], [5.0, -5.0]]]}

    def get_test_polygon_single_tile(self) -> dict[str, Any]:
        return {"type": "Polygon", "coordinates": [[[-5.0, -5.0], [5.0, -5.0], [5.0, 5.0], [-5.0, 5.0], [-5.0, -5.0]]]}

    def test_geojson_specific_features(self):
        """Test GeoJSON-specific features."""
        grid = self.get_grid_system()
        assert isinstance(grid, GeoJSONGridSystem)
        assert len(grid.tiles) == 2
        assert "tile_001" in grid.tiles
        assert "tile_002" in grid.tiles

        # Test point in specific tile
        point_tile1 = {"type": "Point", "coordinates": [0.0, 0.0]}
        tiles = grid.tiles_for_geometry(point_tile1)
        assert tiles == ["tile_001"]

        point_tile2 = {"type": "Point", "coordinates": [20.0, 0.0]}
        tiles = grid.tiles_for_geometry(point_tile2)
        assert tiles == ["tile_002"]

    def test_geojson_error_handling(self):
        """Test GeoJSON error handling."""
        # Test missing file
        with pytest.raises(FileNotFoundError):
            GeoJSONGridSystem("/nonexistent/file.geojson")

        # Test invalid JSON
        invalid_path = Path(self.temp_dir) / "invalid.json"
        with open(invalid_path, "w") as f:
            f.write("invalid json")

        with pytest.raises(ValueError, match="Invalid GeoJSON file"):
            GeoJSONGridSystem(str(invalid_path))

    def test_geojson_multipolygon(self):
        """Test MultiPolygon support."""
        multipolygon_geojson = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "properties": {"id": "multi_tile"},
                    "geometry": {
                        "type": "MultiPolygon",
                        "coordinates": [
                            [[[-10.0, -10.0], [0.0, -10.0], [0.0, 0.0], [-10.0, 0.0], [-10.0, -10.0]]],
                            [[[10.0, 10.0], [20.0, 10.0], [20.0, 20.0], [10.0, 20.0], [10.0, 10.0]]],
                        ],
                    },
                }
            ],
        }

        multipolygon_path = Path(self.temp_dir) / "multi.geojson"
        with open(multipolygon_path, "w") as f:
            json.dump(multipolygon_geojson, f)

        grid = GeoJSONGridSystem(str(multipolygon_path))
        assert len(grid.tiles) == 2
        assert "multi_tile_0" in grid.tiles
        assert "multi_tile_1" in grid.tiles


class TestGridSystemFactory:
    """Test grid system factory function."""

    def test_factory_all_grid_types(self):
        """Test creating all grid types through factory."""
        # Test H3
        h3_grid = get_grid_system("h3", resolution=6)
        assert isinstance(h3_grid, H3GridSystem)
        assert h3_grid.resolution == 6

        # Test S2
        s2_grid = get_grid_system("s2", resolution=13)
        assert isinstance(s2_grid, S2GridSystem)
        assert s2_grid.resolution == 13

        # Test MGRS
        mgrs_grid = get_grid_system("mgrs", resolution=5)
        assert isinstance(mgrs_grid, MGRSGridSystem)
        assert mgrs_grid.resolution == 5

        # Test UTM
        utm_grid = get_grid_system("utm", resolution=1)
        assert isinstance(utm_grid, UTMGridSystem)
        assert utm_grid.resolution == 1

        # Test Lat/Lon
        latlon_grid = get_grid_system("latlon", resolution=1)
        assert isinstance(latlon_grid, SimpleLatLonGrid)
        assert latlon_grid.resolution == 1

        # Test ITS_LIVE
        itslive_grid = get_grid_system("itslive")
        assert isinstance(itslive_grid, ITSLiveGridSystem)
        assert itslive_grid.resolution == 10

    def test_factory_geojson_with_path(self):
        """Test creating GeoJSON grid through factory."""
        temp_dir = tempfile.mkdtemp()

        try:
            # Create test GeoJSON
            test_geojson = {
                "type": "FeatureCollection",
                "features": [
                    {
                        "type": "Feature",
                        "properties": {"id": "test_tile"},
                        "geometry": {
                            "type": "Polygon",
                            "coordinates": [
                                [[-10.0, -10.0], [10.0, -10.0], [10.0, 10.0], [-10.0, 10.0], [-10.0, -10.0]]
                            ],
                        },
                    }
                ],
            }

            geojson_path = Path(temp_dir) / "test.geojson"
            with open(geojson_path, "w") as f:
                json.dump(test_geojson, f)

            grid = get_grid_system("geojson", geojson_path=str(geojson_path))
            assert isinstance(grid, GeoJSONGridSystem)
            assert len(grid.tiles) == 1
            assert "test_tile" in grid.tiles

        finally:
            import shutil

            shutil.rmtree(temp_dir, ignore_errors=True)

    def test_factory_geojson_missing_path(self):
        """Test factory error when geojson_path is missing."""
        with pytest.raises(ValueError, match="geojson_path is required for GeoJSON grid system"):
            get_grid_system("geojson")

    def test_factory_invalid_grid_name(self):
        """Test factory error with invalid grid name."""
        with pytest.raises(ValueError, match="Unknown grid system"):
            get_grid_system("invalid_grid")

    def test_factory_case_insensitive(self):
        """Test that grid names are case insensitive."""
        h3_grid = get_grid_system("H3", resolution=6)
        assert isinstance(h3_grid, H3GridSystem)

        latlon_grid = get_grid_system("LATLON", resolution=1)
        assert isinstance(latlon_grid, SimpleLatLonGrid)


class TestGridSystemComparison:
    """Compare behavior across different grid systems."""

    def test_same_point_different_grids(self):
        """Test same point across different grid systems."""
        point_geom = {
            "type": "Point",
            "coordinates": [-122.4194, 37.7749],  # San Francisco
        }

        # Test grids that should work without additional dependencies
        utm_grid = UTMGridSystem()
        latlon_grid = SimpleLatLonGrid(resolution=1)
        itslive_grid = ITSLiveGridSystem()

        utm_tiles = utm_grid.tiles_for_geometry(point_geom)
        latlon_tiles = latlon_grid.tiles_for_geometry(point_geom)
        itslive_tiles = itslive_grid.tiles_for_geometry(point_geom)

        assert len(utm_tiles) == 1
        assert len(latlon_tiles) == 1
        assert len(itslive_tiles) == 1
        assert all(isinstance(tile, str) for tile in utm_tiles)
        assert all(isinstance(tile, str) for tile in latlon_tiles)
        assert all(isinstance(tile, str) for tile in itslive_tiles)

    def test_polygon_spanning_detection_consistency(self):
        """Test spanning detection across different grid systems."""
        # Large polygon that should span multiple tiles in most systems
        large_polygon = {
            "type": "Polygon",
            "coordinates": [
                [
                    [-125.0, 35.0],
                    [-110.0, 35.0],  # Wide polygon
                    [-110.0, 45.0],  # And tall
                    [-125.0, 45.0],
                    [-125.0, 35.0],
                ]
            ],
        }

        utm_grid = UTMGridSystem()
        latlon_grid = SimpleLatLonGrid(resolution=2)
        itslive_grid = ITSLiveGridSystem()

        utm_tiles, utm_spanning = utm_grid.tiles_for_geometry_with_spanning_detection(large_polygon)
        latlon_tiles, latlon_spanning = latlon_grid.tiles_for_geometry_with_spanning_detection(large_polygon)
        itslive_tiles, itslive_spanning = itslive_grid.tiles_for_geometry_with_spanning_detection(large_polygon)

        # All should detect spanning for this large polygon
        assert len(utm_tiles) >= 1
        assert len(latlon_tiles) >= 1
        assert len(itslive_tiles) >= 1

        # Test that spanning detection works (may be True or False depending on implementation)
        # The important thing is that the method works and returns a boolean
        assert isinstance(latlon_spanning, bool)
        assert isinstance(utm_spanning, bool)
        assert isinstance(itslive_spanning, bool)

    def test_global_partition_thresholds(self):
        """Test global partition thresholds across grid systems."""
        grids = [
            UTMGridSystem(),
            SimpleLatLonGrid(resolution=1),
            ITSLiveGridSystem(),
        ]

        custom_thresholds = {"utm": {"1": 5}, "latlon": {"1": 10}}

        for grid in grids:
            # Test default threshold
            default_threshold = grid.get_global_partition_threshold(100)
            assert default_threshold == 100

            # Test with custom thresholds (implementation dependent)
            custom_threshold = grid.get_global_partition_threshold(50, custom_thresholds)
            assert isinstance(custom_threshold, int)


if __name__ == "__main__":
    pytest.main([__file__])
