"""Tests for idx validation: invalid filters, invalid date ranges, quarter clamping."""

import datetime
import gzip
from pathlib import Path
from typing import Any
from unittest.mock import patch

import pytest
from secbulkdownload import idx


def create_idx_with_mocked_network(datadir: str, **kwargs: Any) -> idx:
    """Create an idx instance with network calls mocked out.

    This prevents the constructor from making network calls during updateidx().
    """
    with patch("secbulkdownload.secmeta.fetch_sec_file"):
        instance = idx(datadir=datadir, **kwargs)
    return instance


class TestInvalidFilterKeys:
    """Test that invalid filter column names are rejected."""

    def test_invalid_filter_key_raises_error(self, datadir: str) -> None:
        """Filter with unknown column name should raise KeyError."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2024,
            start_quarter=1,
            end_year=2024,
            end_quarter=1,
            user_agent="Test Co test@test.com",
        )

        # Create a simple mock index file
        idxdir = Path(datadir) / "idxfiles" / "2024" / "QTR1"
        idxdir.mkdir(parents=True, exist_ok=True)

        # Create the file so validation runs
        gz_path = idxdir / "master.gz"
        with gzip.open(gz_path, "wt", encoding="latin-1") as f:
            f.write("Description: Test\nCIK|Company Name|Form Type|Date Filed|Filename\n")
            f.write("0000320193|APPLE INC|10-K|2024-01-01|edgar/data/test.txt\n")

        idx_instance.idxlist = [str(gz_path)]

        with pytest.raises(KeyError, match="Invalid filter key"):
            idx_instance.filter_index({"InvalidColumn": ["test"]})

    def test_invalid_filter_key_names_the_invalid_key(self, datadir: str) -> None:
        """Error message should name the invalid key."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2024,
            start_quarter=1,
            end_year=2024,
            end_quarter=1,
            user_agent="Test Co test@test.com",
        )

        idxdir = Path(datadir) / "idxfiles" / "2024" / "QTR1"
        idxdir.mkdir(parents=True, exist_ok=True)

        gz_path = idxdir / "master.gz"
        with gzip.open(gz_path, "wt", encoding="latin-1") as f:
            f.write("Description: Test\nCIK|Company Name|Form Type|Date Filed|Filename\n")
            f.write("0000320193|APPLE INC|10-K|2024-01-01|edgar/data/test.txt\n")

        idx_instance.idxlist = [str(gz_path)]

        with pytest.raises(KeyError, match="BadColumnName"):
            idx_instance.filter_index({"BadColumnName": ["value"]})

    def test_partial_invalid_keys_still_fail(self, datadir: str) -> None:
        """Filter with mix of valid and invalid keys should still fail."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2024,
            start_quarter=1,
            end_year=2024,
            end_quarter=1,
            user_agent="Test Co test@test.com",
        )

        idxdir = Path(datadir) / "idxfiles" / "2024" / "QTR1"
        idxdir.mkdir(parents=True, exist_ok=True)

        gz_path = idxdir / "master.gz"
        with gzip.open(gz_path, "wt", encoding="latin-1") as f:
            f.write("Description: Test\nCIK|Company Name|Form Type|Date Filed|Filename\n")
            f.write("0000320193|APPLE INC|10-K|2024-01-01|edgar/data/test.txt\n")

        idx_instance.idxlist = [str(gz_path)]

        # Valid key + invalid key
        with pytest.raises(KeyError):
            idx_instance.filter_index({"Form Type": ["10-K"], "InvalidKey": ["test"]})


class TestQuarterClamping:
    """Test that end_quarter is clamped to current SEC quarter."""

    def test_future_quarter_clamped_to_current(self, datadir: str) -> None:
        """If end_quarter is in the future, it should be clamped to current quarter."""
        # Get current quarter
        today = datetime.date.today()
        current_qtr = (today.month - 1) // 3 + 1
        future_qtr = current_qtr + 1 if current_qtr < 4 else 1
        future_year = today.year if current_qtr < 4 else today.year + 1

        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=today.year,
            start_quarter=1,
            end_year=future_year,
            end_quarter=future_qtr,
            user_agent="Test Co test@test.com",
        )

        # The clamped end_qtr should be <= current_qtr
        assert idx_instance.end_qtr <= current_qtr

    def test_current_year_future_quarter_clamped(self, datadir: str) -> None:
        """End quarter in current year but future quarter should be clamped."""
        today = datetime.date.today()
        current_qtr = (today.month - 1) // 3 + 1

        # Request Q4 when we might be in Q1
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=today.year,
            start_quarter=1,
            end_year=today.year,
            end_quarter=4,  # May be in future
            user_agent="Test Co test@test.com",
        )

        assert idx_instance.end_qtr <= current_qtr

    def test_end_year_zero_uses_current_year(self, datadir: str) -> None:
        """end_year=0 should resolve to current year."""
        today = datetime.date.today()

        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=1993,
            start_quarter=1,
            end_year=0,  # Should become current year
            end_quarter=4,
            user_agent="Test Co test@test.com",
        )

        assert idx_instance.end_yr == today.year


class TestInvalidDateRanges:
    """Test that invalid date ranges are rejected before network calls."""

    def test_invalid_quarter_number_raises_error(self, datadir: str) -> None:
        """Quarter outside 1-4 should raise error."""
        with pytest.raises(ValueError, match="quarter"):
            idx(
                datadir=datadir,
                start_year=2024,
                start_quarter=5,  # Invalid
                end_year=2024,
                end_quarter=1,
                user_agent="Test Co test@test.com",
            )

    def test_zero_quarter_raises_error(self, datadir: str) -> None:
        """Quarter 0 should raise error."""
        with pytest.raises(ValueError, match="quarter"):
            idx(
                datadir=datadir,
                start_year=2024,
                start_quarter=0,  # Invalid
                end_year=2024,
                end_quarter=1,
                user_agent="Test Co test@test.com",
            )

    def test_start_after_end_raises_error(self, datadir: str) -> None:
        """Start date after end date should raise error."""
        with pytest.raises(ValueError, match="start.*before.*end|date range"):
            idx(
                datadir=datadir,
                start_year=2024,
                start_quarter=2,
                end_year=2024,
                end_quarter=1,  # Before start
                user_agent="Test Co test@test.com",
            )

    def test_start_year_after_end_year_raises_error(self, datadir: str) -> None:
        """Start year after end year should raise error."""
        with pytest.raises(ValueError, match="start.*before.*end|date range"):
            idx(
                datadir=datadir,
                start_year=2025,
                start_quarter=1,
                end_year=2024,  # Before start
                end_quarter=4,
                user_agent="Test Co test@test.com",
            )

    def test_negative_year_raises_error(self, datadir: str) -> None:
        """Negative year should raise error."""
        with pytest.raises(ValueError, match="year"):
            idx(
                datadir=datadir,
                start_year=-1,
                start_quarter=1,
                end_year=2024,
                end_quarter=1,
                user_agent="Test Co test@test.com",
            )

    def test_year_before_sec_online_filing_raises_error(self, datadir: str) -> None:
        """Year before 1993 (SEC online filing start) should raise error or warn."""
        # This could be a warning or error - we'll accept either behavior
        # The spec says start_year defaults to 1993
        _idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=1990,  # Before SEC online
            start_quarter=1,
            end_year=2024,
            end_quarter=1,
            user_agent="Test Co test@test.com",
        )
        # If it doesn't raise, it should at least clamp or handle gracefully
        # This test documents the behavior


class TestQuarterURLResolution:
    """Test that quarter URLs are correctly resolved."""

    def test_inclusive_quarter_range(self, datadir: str) -> None:
        """Quarter range should be inclusive of both start and end."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2023,
            start_quarter=3,
            end_year=2024,
            end_quarter=2,
            user_agent="Test Co test@test.com",
        )

        # Check that _idxurls generates correct URLs
        urls = idx_instance._idxurls()

        # Should include 2023 Q3, Q4 and 2024 Q1, Q2
        assert len(urls) == 4
        assert "2023/QTR3" in urls[0]
        assert "2023/QTR4" in urls[1]
        assert "2024/QTR1" in urls[2]
        assert "2024/QTR2" in urls[3]

    def test_single_quarter_range(self, datadir: str) -> None:
        """Single quarter range should produce one URL."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2024,
            start_quarter=1,
            end_year=2024,
            end_quarter=1,
            user_agent="Test Co test@test.com",
        )

        urls = idx_instance._idxurls()
        assert len(urls) == 1
        assert "2024/QTR1" in urls[0]

    def test_full_year_range(self, datadir: str) -> None:
        """Full year range should produce 4 URLs per year."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2024,
            start_quarter=1,
            end_year=2024,
            end_quarter=4,
            user_agent="Test Co test@test.com",
        )

        urls = idx_instance._idxurls()
        assert len(urls) == 4


class TestCachePaths:
    """Test that local cache paths follow canonical format."""

    def test_cache_path_format(self, datadir: str) -> None:
        """Cache paths should follow <datadir>/idxfiles/YYYY/QTRn/master.gz format."""
        idx_instance = create_idx_with_mocked_network(
            datadir=datadir,
            start_year=2024,
            start_quarter=1,
            end_year=2024,
            end_quarter=1,
            user_agent="Test Co test@test.com",
        )

        # Get the expected cache path for 2024 Q1
        urls = idx_instance._idxurls()
        expected_paths = [p.replace("//", "/") for p in idx_instance._expected_cache_paths(urls)]

        # Check format
        assert len(expected_paths) == 1
        assert "/idxfiles/2024/QTR1/master.gz" in expected_paths[0]
