"""Tests for the exceptions module."""

import pytest

from earthcatalog.exceptions import (
    ConfigurationError,
    ConsolidationError,
    DownloadError,
    EarthCatalogError,
    IngestionError,
    InvalidGridConfigError,
    InvalidStorageConfigError,
    ItemValidationError,
    QueryError,
    SpatialResolverError,
    StorageConnectionError,
    StorageError,
    StorageReadError,
    StorageWriteError,
)


class TestEarthCatalogError:
    """Tests for base EarthCatalogError class."""

    def test_basic_initialization(self):
        """Test basic exception initialization."""
        exc = EarthCatalogError("Something went wrong")
        assert str(exc) == "Something went wrong"
        assert exc.message == "Something went wrong"
        assert exc.details == {}

    def test_initialization_with_details(self):
        """Test exception with details dict."""
        details = {"key": "value", "count": 42}
        exc = EarthCatalogError("Error occurred", details=details)
        assert exc.details == details
        assert exc.details["key"] == "value"

    def test_repr(self):
        """Test __repr__ method."""
        exc = EarthCatalogError("Test error")
        assert repr(exc) == "EarthCatalogError('Test error')"

    def test_bool(self):
        """Test __bool__ method - exceptions are always truthy."""
        exc = EarthCatalogError("Error")
        assert bool(exc) is True

    def test_is_exception(self):
        """Test that it can be raised and caught."""
        with pytest.raises(EarthCatalogError):
            raise EarthCatalogError("Test")

    def test_catch_as_base_exception(self):
        """Test that exception inherits from Exception."""
        exc = EarthCatalogError("Test")
        assert isinstance(exc, Exception)


class TestConfigurationError:
    """Tests for ConfigurationError and subclasses."""

    def test_configuration_error_inheritance(self):
        """Test ConfigurationError inherits from EarthCatalogError."""
        exc = ConfigurationError("Invalid config")
        assert isinstance(exc, EarthCatalogError)

    def test_invalid_grid_config_error(self):
        """Test InvalidGridConfigError with all attributes."""
        exc = InvalidGridConfigError(
            "H3 resolution must be 0-15",
            grid_system="h3",
            resolution=20,
        )
        assert exc.grid_system == "h3"
        assert exc.resolution == 20
        assert exc.details["grid_system"] == "h3"
        assert exc.details["resolution"] == 20
        assert isinstance(exc, ConfigurationError)

    def test_invalid_grid_config_error_with_extra_kwargs(self):
        """Test InvalidGridConfigError with additional kwargs."""
        exc = InvalidGridConfigError(
            "Error",
            grid_system="h3",
            resolution=6,
            extra_info="some value",
        )
        assert exc.details["extra_info"] == "some value"

    def test_invalid_storage_config_error(self):
        """Test InvalidStorageConfigError with all attributes."""
        exc = InvalidStorageConfigError(
            "Bucket does not exist",
            backend="s3",
            path="s3://nonexistent/bucket",
        )
        assert exc.backend == "s3"
        assert exc.path == "s3://nonexistent/bucket"
        assert isinstance(exc, ConfigurationError)


class TestIngestionError:
    """Tests for IngestionError and subclasses."""

    def test_ingestion_error_inheritance(self):
        """Test IngestionError inherits from EarthCatalogError."""
        exc = IngestionError("Ingestion failed")
        assert isinstance(exc, EarthCatalogError)

    def test_download_error_basic(self):
        """Test DownloadError with minimal attributes."""
        exc = DownloadError("Connection failed")
        assert exc.url is None
        assert exc.status_code is None
        assert exc.retry_count == 0
        assert isinstance(exc, IngestionError)

    def test_download_error_full(self):
        """Test DownloadError with all attributes."""
        exc = DownloadError(
            "Request timed out",
            url="https://api.example.com/item.json",
            status_code=504,
            retry_count=3,
            error_type="timeout",
        )
        assert exc.url == "https://api.example.com/item.json"
        assert exc.status_code == 504
        assert exc.retry_count == 3
        assert exc.error_type == "timeout"
        assert exc.details["url"] == "https://api.example.com/item.json"

    def test_item_validation_error(self):
        """Test ItemValidationError with all attributes."""
        exc = ItemValidationError(
            "Geometry is self-intersecting",
            item_id="ITEM_123",
            issue_code="INVALID_GEOMETRY",
        )
        assert exc.item_id == "ITEM_123"
        assert exc.issue_code == "INVALID_GEOMETRY"
        assert isinstance(exc, IngestionError)

    def test_consolidation_error(self):
        """Test ConsolidationError with all attributes."""
        exc = ConsolidationError(
            "Memory limit exceeded",
            partition_key="h3=abc123/year=2024/month=01",
            shard_count=50,
        )
        assert exc.partition_key == "h3=abc123/year=2024/month=01"
        assert exc.shard_count == 50
        assert isinstance(exc, IngestionError)


class TestStorageError:
    """Tests for StorageError and subclasses."""

    def test_storage_error_inheritance(self):
        """Test StorageError inherits from EarthCatalogError."""
        exc = StorageError("Storage failed")
        assert isinstance(exc, EarthCatalogError)

    def test_storage_error_with_path(self):
        """Test StorageError with path attribute."""
        exc = StorageError("Cannot access path", path="/data/catalog")
        assert exc.path == "/data/catalog"
        assert exc.details["path"] == "/data/catalog"

    def test_storage_connection_error(self):
        """Test StorageConnectionError."""
        exc = StorageConnectionError(
            "Access denied",
            path="s3://bucket/catalog",
        )
        assert exc.path == "s3://bucket/catalog"
        assert isinstance(exc, StorageError)

    def test_storage_write_error(self):
        """Test StorageWriteError with bytes_written."""
        exc = StorageWriteError(
            "Disk full",
            path="/catalog/partition.parquet",
            bytes_written=1024000,
        )
        assert exc.path == "/catalog/partition.parquet"
        assert exc.bytes_written == 1024000
        assert exc.details["bytes_written"] == 1024000
        assert isinstance(exc, StorageError)

    def test_storage_read_error(self):
        """Test StorageReadError."""
        exc = StorageReadError(
            "File not found",
            path="s3://bucket/missing.parquet",
        )
        assert exc.path == "s3://bucket/missing.parquet"
        assert isinstance(exc, StorageError)


class TestQueryError:
    """Tests for QueryError and subclasses."""

    def test_query_error_inheritance(self):
        """Test QueryError inherits from EarthCatalogError."""
        exc = QueryError("Query failed")
        assert isinstance(exc, EarthCatalogError)

    def test_spatial_resolver_error(self):
        """Test SpatialResolverError with geometry_type."""
        exc = SpatialResolverError(
            "Cannot resolve partitions",
            geometry_type="Polygon",
        )
        assert exc.geometry_type == "Polygon"
        assert exc.details["geometry_type"] == "Polygon"
        assert isinstance(exc, QueryError)


class TestExceptionHierarchy:
    """Tests for the exception hierarchy."""

    def test_all_inherit_from_base(self):
        """Test all exceptions inherit from EarthCatalogError."""
        exceptions = [
            ConfigurationError("test"),
            InvalidGridConfigError("test"),
            InvalidStorageConfigError("test"),
            IngestionError("test"),
            DownloadError("test"),
            ItemValidationError("test"),
            ConsolidationError("test"),
            StorageError("test"),
            StorageConnectionError("test"),
            StorageWriteError("test"),
            StorageReadError("test"),
            QueryError("test"),
            SpatialResolverError("test"),
        ]
        for exc in exceptions:
            assert isinstance(exc, EarthCatalogError), f"{type(exc).__name__} should inherit from EarthCatalogError"

    def test_catch_all_with_base(self):
        """Test that all exceptions can be caught with base class."""
        exceptions_to_test = [
            DownloadError("test"),
            ConsolidationError("test"),
            StorageWriteError("test"),
            SpatialResolverError("test"),
        ]

        for exc in exceptions_to_test:
            try:
                raise exc
            except EarthCatalogError as caught:
                assert caught is exc

    @pytest.mark.parametrize(
        "exception_class,parent_class",
        [
            # Configuration errors
            (InvalidGridConfigError, ConfigurationError),
            (InvalidStorageConfigError, ConfigurationError),
            # Ingestion errors
            (DownloadError, IngestionError),
            (ItemValidationError, IngestionError),
            (ConsolidationError, IngestionError),
            # Storage errors
            (StorageConnectionError, StorageError),
            (StorageWriteError, StorageError),
            (StorageReadError, StorageError),
            # Query errors
            (SpatialResolverError, QueryError),
        ],
        ids=[
            "InvalidGridConfigError->ConfigurationError",
            "InvalidStorageConfigError->ConfigurationError",
            "DownloadError->IngestionError",
            "ItemValidationError->IngestionError",
            "ConsolidationError->IngestionError",
            "StorageConnectionError->StorageError",
            "StorageWriteError->StorageError",
            "StorageReadError->StorageError",
            "SpatialResolverError->QueryError",
        ],
    )
    def test_inheritance_chains(self, exception_class, parent_class):
        """Test specific inheritance chains."""
        exc = exception_class("test")
        assert isinstance(exc, parent_class), f"{exception_class.__name__} should inherit from {parent_class.__name__}"


class TestExceptionUsage:
    """Test realistic usage patterns."""

    def test_exception_in_try_except(self):
        """Test exception usage in try/except."""

        def download_item(url: str) -> dict:
            if "invalid" in url:
                raise DownloadError(
                    f"Failed to download {url}",
                    url=url,
                    status_code=404,
                    error_type="not_found",
                )
            return {"id": "item"}

        # Should not raise
        result = download_item("https://valid.com/item.json")
        assert result == {"id": "item"}

        # Should raise
        with pytest.raises(DownloadError) as exc_info:
            download_item("https://invalid.com/item.json")

        assert exc_info.value.status_code == 404
        assert exc_info.value.error_type == "not_found"

    def test_exception_chaining(self):
        """Test exception chaining with __cause__."""
        try:
            try:
                raise ValueError("Original error")
            except ValueError as e:
                raise DownloadError("Download failed") from e
        except DownloadError as exc:
            assert exc.__cause__ is not None
            assert isinstance(exc.__cause__, ValueError)

    def test_exception_details_for_logging(self):
        """Test that exception details are useful for logging."""
        exc = ConsolidationError(
            "Failed to merge shards",
            partition_key="h3=abc/year=2024",
            shard_count=10,
            memory_used_mb=1024,
        )

        # Details should be suitable for structured logging
        log_context = exc.details
        assert "partition_key" in log_context
        assert "shard_count" in log_context
        assert "memory_used_mb" in log_context
