"""Unit tests for CacheManager."""

import os
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest.mock import patch

import pytest

from teams_phone.constants import CSV_STALE_DAYS
from teams_phone.exceptions import ConfigurationError
from teams_phone.infrastructure import CacheManager
from teams_phone.models import CachedToken, CacheMetadata


class TestCacheManagerInit:
    """Tests for CacheManager initialization."""

    @pytest.mark.unit
    def test_default_paths(self) -> None:
        """CacheManager uses default paths when not specified."""
        manager = CacheManager()
        assert manager.cache_dir == Path.home() / ".teams-phone" / "cache"
        assert manager.token_dir == Path.home() / ".teams-phone" / "tokens"

    @pytest.mark.unit
    def test_custom_paths(self, tmp_path: Path) -> None:
        """CacheManager accepts custom cache and token paths."""
        cache_path = tmp_path / "custom" / "cache"
        token_path = tmp_path / "custom" / "tokens"
        manager = CacheManager(cache_dir=str(cache_path), token_dir=str(token_path))

        assert manager.cache_dir == cache_path
        assert manager.token_dir == token_path

    @pytest.mark.unit
    def test_expands_tilde_in_cache_dir(self) -> None:
        """CacheManager expands ~ in cache_dir path."""
        manager = CacheManager(cache_dir="~/my-cache")
        assert manager.cache_dir == Path.home() / "my-cache"

    @pytest.mark.unit
    def test_expands_tilde_in_token_dir(self) -> None:
        """CacheManager expands ~ in token_dir path."""
        manager = CacheManager(token_dir="~/my-tokens")
        assert manager.token_dir == Path.home() / "my-tokens"


class TestEnsureTokenDir:
    """Tests for ensure_token_dir method."""

    @pytest.mark.unit
    def test_creates_parent_directories(self, tmp_path: Path) -> None:
        """ensure_token_dir creates parent directories."""
        token_path = tmp_path / "deep" / "nested" / "tokens"
        manager = CacheManager(token_dir=str(token_path))

        assert not token_path.exists()
        manager.ensure_token_dir()
        assert token_path.exists()

    @pytest.mark.unit
    def test_idempotent(self, tmp_path: Path) -> None:
        """ensure_token_dir is idempotent."""
        token_path = tmp_path / "tokens"
        manager = CacheManager(token_dir=str(token_path))

        manager.ensure_token_dir()
        manager.ensure_token_dir()  # Should not raise
        assert token_path.exists()

    @pytest.mark.unit
    def test_permission_error_raises_configuration_error(self, tmp_path: Path) -> None:
        """ensure_token_dir raises ConfigurationError on permission failure."""
        token_path = tmp_path / "restricted" / "tokens"
        manager = CacheManager(token_dir=str(token_path))

        with patch.object(Path, "mkdir", side_effect=PermissionError("Access denied")):
            with pytest.raises(ConfigurationError) as exc_info:
                manager.ensure_token_dir()

        assert "Cannot create token directory" in str(exc_info.value)
        assert exc_info.value.remediation is not None
        assert "permission" in exc_info.value.remediation.lower()


class TestGetToken:
    """Tests for get_token method."""

    @pytest.mark.unit
    def test_returns_none_for_missing_file(self, tmp_path: Path) -> None:
        """get_token returns None when token file doesn't exist."""
        manager = CacheManager(token_dir=str(tmp_path))

        result = manager.get_token("nonexistent")

        assert result is None

    @pytest.mark.unit
    def test_returns_cached_token(self, tmp_path: Path) -> None:
        """get_token returns CachedToken from valid JSON file."""
        manager = CacheManager(token_dir=str(tmp_path))
        token_file = tmp_path / "contoso.json"
        expires_at = datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
        token_file.write_text(
            f"""{{
                "access_token": "test-token-123",
                "expires_at": "{expires_at.isoformat()}",
                "tenant_id": "tenant-id-abc",
                "scopes": ["User.Read", "Team.ReadBasic.All"]
            }}""",
            encoding="utf-8",
        )

        result = manager.get_token("contoso")

        assert result is not None
        assert result.access_token == "test-token-123"
        assert result.tenant_id == "tenant-id-abc"
        assert result.scopes == ["User.Read", "Team.ReadBasic.All"]

    @pytest.mark.unit
    def test_returns_none_for_invalid_json(self, tmp_path: Path) -> None:
        """get_token returns None for invalid JSON content."""
        manager = CacheManager(token_dir=str(tmp_path))
        token_file = tmp_path / "contoso.json"
        token_file.write_text("not valid json {", encoding="utf-8")

        result = manager.get_token("contoso")

        assert result is None

    @pytest.mark.unit
    def test_returns_none_for_invalid_schema(self, tmp_path: Path) -> None:
        """get_token returns None when JSON doesn't match CachedToken schema."""
        manager = CacheManager(token_dir=str(tmp_path))
        token_file = tmp_path / "contoso.json"
        token_file.write_text(
            '{"invalid_field": "missing required fields"}',
            encoding="utf-8",
        )

        result = manager.get_token("contoso")

        assert result is None

    @pytest.mark.unit
    def test_returns_none_for_extra_fields(self, tmp_path: Path) -> None:
        """get_token returns None when JSON has extra fields (forbid mode)."""
        manager = CacheManager(token_dir=str(tmp_path))
        token_file = tmp_path / "contoso.json"
        expires_at = datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc)
        token_file.write_text(
            f"""{{
                "access_token": "test-token",
                "expires_at": "{expires_at.isoformat()}",
                "tenant_id": "tenant-id",
                "scopes": ["User.Read"],
                "extra_field": "should fail"
            }}""",
            encoding="utf-8",
        )

        result = manager.get_token("contoso")

        assert result is None


class TestSaveToken:
    """Tests for save_token method."""

    @pytest.mark.unit
    def test_saves_token_to_file(self, tmp_path: Path) -> None:
        """save_token writes token to JSON file."""
        manager = CacheManager(token_dir=str(tmp_path))
        token = CachedToken(
            access_token="my-access-token",
            expires_at=datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc),
            tenant_id="tenant-123",
            scopes=["User.Read"],
        )

        manager.save_token("contoso", token)

        token_file = tmp_path / "contoso.json"
        assert token_file.exists()
        content = token_file.read_text(encoding="utf-8")
        assert "my-access-token" in content
        assert "tenant-123" in content

    @pytest.mark.unit
    def test_creates_token_directory(self, tmp_path: Path) -> None:
        """save_token creates token directory if it doesn't exist."""
        token_path = tmp_path / "deep" / "nested" / "tokens"
        manager = CacheManager(token_dir=str(token_path))
        token = CachedToken(
            access_token="test-token",
            expires_at=datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc),
            tenant_id="tenant-123",
            scopes=["User.Read"],
        )

        assert not token_path.exists()
        manager.save_token("contoso", token)
        assert token_path.exists()

    @pytest.mark.unit
    def test_overwrites_existing_token(self, tmp_path: Path) -> None:
        """save_token overwrites existing token file."""
        manager = CacheManager(token_dir=str(tmp_path))
        token_file = tmp_path / "contoso.json"
        token_file.write_text("old content", encoding="utf-8")

        new_token = CachedToken(
            access_token="new-token",
            expires_at=datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc),
            tenant_id="tenant-123",
            scopes=["User.Read"],
        )
        manager.save_token("contoso", new_token)

        content = token_file.read_text(encoding="utf-8")
        assert "old content" not in content
        assert "new-token" in content

    @pytest.mark.unit
    def test_round_trip(self, tmp_path: Path) -> None:
        """Token survives save then get round-trip."""
        manager = CacheManager(token_dir=str(tmp_path))
        original = CachedToken(
            access_token="round-trip-token",
            expires_at=datetime(2024, 12, 31, 23, 59, 59, tzinfo=timezone.utc),
            tenant_id="tenant-abc",
            scopes=["User.Read", "Mail.Read"],
        )

        manager.save_token("test-tenant", original)
        loaded = manager.get_token("test-tenant")

        assert loaded is not None
        assert loaded.access_token == original.access_token
        assert loaded.tenant_id == original.tenant_id
        assert loaded.scopes == original.scopes
        # Compare datetime values (may differ in microseconds due to serialization)
        assert loaded.expires_at.replace(microsecond=0) == original.expires_at.replace(
            microsecond=0
        )


class TestClearTokens:
    """Tests for clear_tokens method."""

    @pytest.mark.unit
    def test_clears_specific_tenant_token(self, tmp_path: Path) -> None:
        """clear_tokens removes specific tenant's token file."""
        manager = CacheManager(token_dir=str(tmp_path))
        token_file = tmp_path / "contoso.json"
        token_file.write_text("{}", encoding="utf-8")

        assert token_file.exists()
        manager.clear_tokens("contoso")
        assert not token_file.exists()

    @pytest.mark.unit
    def test_clears_missing_token_silently(self, tmp_path: Path) -> None:
        """clear_tokens doesn't raise for non-existent token file."""
        manager = CacheManager(token_dir=str(tmp_path))

        # Should not raise
        manager.clear_tokens("nonexistent")

    @pytest.mark.unit
    def test_clears_all_tokens(self, tmp_path: Path) -> None:
        """clear_tokens with None removes all JSON files."""
        manager = CacheManager(token_dir=str(tmp_path))

        # Create multiple token files
        (tmp_path / "contoso.json").write_text("{}", encoding="utf-8")
        (tmp_path / "fabrikam.json").write_text("{}", encoding="utf-8")
        (tmp_path / "woodgrove.json").write_text("{}", encoding="utf-8")

        manager.clear_tokens(None)

        assert not (tmp_path / "contoso.json").exists()
        assert not (tmp_path / "fabrikam.json").exists()
        assert not (tmp_path / "woodgrove.json").exists()

    @pytest.mark.unit
    def test_preserves_non_json_files(self, tmp_path: Path) -> None:
        """clear_tokens(None) only removes .json files."""
        manager = CacheManager(token_dir=str(tmp_path))

        # Create mixed files
        (tmp_path / "contoso.json").write_text("{}", encoding="utf-8")
        (tmp_path / "other.txt").write_text("keep me", encoding="utf-8")
        (tmp_path / ".gitkeep").write_text("", encoding="utf-8")

        manager.clear_tokens(None)

        assert not (tmp_path / "contoso.json").exists()
        assert (tmp_path / "other.txt").exists()
        assert (tmp_path / ".gitkeep").exists()

    @pytest.mark.unit
    def test_preserves_token_directory(self, tmp_path: Path) -> None:
        """clear_tokens(None) keeps the token directory."""
        manager = CacheManager(token_dir=str(tmp_path))
        (tmp_path / "contoso.json").write_text("{}", encoding="utf-8")

        manager.clear_tokens(None)

        assert tmp_path.exists()

    @pytest.mark.unit
    def test_handles_missing_token_directory(self, tmp_path: Path) -> None:
        """clear_tokens(None) handles non-existent token directory."""
        missing_path = tmp_path / "nonexistent" / "tokens"
        manager = CacheManager(token_dir=str(missing_path))

        # Should not raise
        manager.clear_tokens(None)

    @pytest.mark.unit
    def test_preserves_other_tenant_tokens(self, tmp_path: Path) -> None:
        """clear_tokens with specific tenant preserves other tenants."""
        manager = CacheManager(token_dir=str(tmp_path))

        (tmp_path / "contoso.json").write_text("{}", encoding="utf-8")
        (tmp_path / "fabrikam.json").write_text("{}", encoding="utf-8")

        manager.clear_tokens("contoso")

        assert not (tmp_path / "contoso.json").exists()
        assert (tmp_path / "fabrikam.json").exists()


# Sample CSV content for tests
LOCATIONS_CSV = """locationId,civicAddressId,description,companyName,address,city,stateOrProvince,postalCode,countryOrRegion,isDefault
93cb8a70-b4af-41df-9928-d07607e21776,a1b2c3d4-0000-0000-0000-000000000000,Main Office - Floor 3,Contoso Ltd,123 Main St,Seattle,WA,98101,US,True
f47ac10b-58cc-4372-a567-0e02b2c3d479,a1b2c3d4-0000-0000-0000-000000000000,Branch Office,Contoso Ltd,456 Oak Ave,Portland,OR,97201,US,False"""

POLICIES_CSV = """policyType,policyName,policyId,description,isGlobal
TeamsCallingPolicy,Global,Global,Default calling policy,True
TeamsCallingPolicy,AllowCalling,AllowCalling,Allows all calling features,False
TeamsVoicemailPolicy,Global,Global,Default voicemail policy,True"""

# UTF-8 BOM bytes
UTF8_BOM = b"\xef\xbb\xbf"


class TestReadLocationsCsv:
    """Tests for read_locations_csv method."""

    @pytest.mark.unit
    def test_returns_list_of_dicts(self, tmp_path: Path) -> None:
        """read_locations_csv returns list of dicts with correct keys."""
        manager = CacheManager(cache_dir=str(tmp_path))
        (tmp_path / "locations.csv").write_text(LOCATIONS_CSV, encoding="utf-8")

        result = manager.read_locations_csv()

        assert len(result) == 2
        assert result[0]["locationId"] == "93cb8a70-b4af-41df-9928-d07607e21776"
        assert result[0]["description"] == "Main Office - Floor 3"
        assert result[0]["city"] == "Seattle"
        assert result[1]["city"] == "Portland"

    @pytest.mark.unit
    def test_returns_empty_list_for_missing_file(self, tmp_path: Path) -> None:
        """read_locations_csv returns empty list when file doesn't exist."""
        manager = CacheManager(cache_dir=str(tmp_path))

        result = manager.read_locations_csv()

        assert result == []

    @pytest.mark.unit
    def test_handles_utf8_bom(self, tmp_path: Path) -> None:
        """read_locations_csv handles UTF-8 BOM from PowerShell exports."""
        manager = CacheManager(cache_dir=str(tmp_path))
        csv_path = tmp_path / "locations.csv"
        csv_path.write_bytes(UTF8_BOM + LOCATIONS_CSV.encode("utf-8"))

        result = manager.read_locations_csv()

        assert len(result) == 2
        assert result[0]["locationId"] == "93cb8a70-b4af-41df-9928-d07607e21776"


class TestReadPoliciesCsv:
    """Tests for read_policies_csv method."""

    @pytest.mark.unit
    def test_returns_list_of_dicts(self, tmp_path: Path) -> None:
        """read_policies_csv returns list of dicts with correct keys."""
        manager = CacheManager(cache_dir=str(tmp_path))
        (tmp_path / "policies.csv").write_text(POLICIES_CSV, encoding="utf-8")

        result = manager.read_policies_csv()

        assert len(result) == 3
        assert result[0]["policyType"] == "TeamsCallingPolicy"
        assert result[0]["policyName"] == "Global"
        assert result[0]["isGlobal"] == "True"
        assert result[1]["policyName"] == "AllowCalling"

    @pytest.mark.unit
    def test_returns_empty_list_for_missing_file(self, tmp_path: Path) -> None:
        """read_policies_csv returns empty list when file doesn't exist."""
        manager = CacheManager(cache_dir=str(tmp_path))

        result = manager.read_policies_csv()

        assert result == []

    @pytest.mark.unit
    def test_handles_utf8_bom(self, tmp_path: Path) -> None:
        """read_policies_csv handles UTF-8 BOM from PowerShell exports."""
        manager = CacheManager(cache_dir=str(tmp_path))
        csv_path = tmp_path / "policies.csv"
        csv_path.write_bytes(UTF8_BOM + POLICIES_CSV.encode("utf-8"))

        result = manager.read_policies_csv()

        assert len(result) == 3
        assert result[0]["policyType"] == "TeamsCallingPolicy"


class TestReadCsvHelper:
    """Tests for _read_csv internal method."""

    @pytest.mark.unit
    def test_returns_empty_list_for_empty_file(self, tmp_path: Path) -> None:
        """_read_csv returns empty list for empty file."""
        manager = CacheManager(cache_dir=str(tmp_path))
        (tmp_path / "empty.csv").write_text("", encoding="utf-8")

        result = manager._read_csv("empty.csv")

        assert result == []

    @pytest.mark.unit
    def test_returns_empty_list_for_header_only(self, tmp_path: Path) -> None:
        """_read_csv returns empty list for CSV with only header."""
        manager = CacheManager(cache_dir=str(tmp_path))
        (tmp_path / "header_only.csv").write_text("col1,col2,col3\n", encoding="utf-8")

        result = manager._read_csv("header_only.csv")

        assert result == []

    @pytest.mark.unit
    def test_handles_csv_with_special_characters(self, tmp_path: Path) -> None:
        """_read_csv handles CSV with special characters in values."""
        manager = CacheManager(cache_dir=str(tmp_path))
        csv_content = '''name,description
"Test, Inc.","A company with a comma"
"Quote ""Company""","Has quotes"'''
        (tmp_path / "special.csv").write_text(csv_content, encoding="utf-8")

        result = manager._read_csv("special.csv")

        assert len(result) == 2
        assert result[0]["name"] == "Test, Inc."
        assert result[0]["description"] == "A company with a comma"
        assert result[1]["name"] == 'Quote "Company"'

    @pytest.mark.unit
    def test_handles_newlines_in_values(self, tmp_path: Path) -> None:
        """_read_csv handles newlines within quoted values."""
        manager = CacheManager(cache_dir=str(tmp_path))
        csv_content = '''name,description
"Company","First line
Second line"'''
        (tmp_path / "newlines.csv").write_text(csv_content, encoding="utf-8")

        result = manager._read_csv("newlines.csv")

        assert len(result) == 1
        assert "Second line" in result[0]["description"]

    @pytest.mark.unit
    def test_different_csv_file(self, tmp_path: Path) -> None:
        """_read_csv works with arbitrary filenames."""
        manager = CacheManager(cache_dir=str(tmp_path))
        (tmp_path / "custom.csv").write_text("a,b\n1,2\n", encoding="utf-8")

        result = manager._read_csv("custom.csv")

        assert len(result) == 1
        assert result[0]["a"] == "1"
        assert result[0]["b"] == "2"


class TestGetCsvMetadata:
    """Tests for get_csv_metadata method."""

    @pytest.mark.unit
    def test_returns_none_when_no_csv_files(self, tmp_path: Path) -> None:
        """get_csv_metadata returns None when neither CSV file exists."""
        manager = CacheManager(cache_dir=str(tmp_path))

        result = manager.get_csv_metadata()

        assert result is None

    @pytest.mark.unit
    def test_returns_correct_counts_and_mtime(self, tmp_path: Path) -> None:
        """get_csv_metadata returns correct counts and oldest mtime."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create both CSV files
        locations_path = tmp_path / "locations.csv"
        policies_path = tmp_path / "policies.csv"
        locations_path.write_text(LOCATIONS_CSV, encoding="utf-8")
        policies_path.write_text(POLICIES_CSV, encoding="utf-8")

        # Set different mtimes - locations older
        older_time = datetime(2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc).timestamp()
        newer_time = datetime(2024, 1, 20, 12, 0, 0, tzinfo=timezone.utc).timestamp()
        os.utime(locations_path, (older_time, older_time))
        os.utime(policies_path, (newer_time, newer_time))

        result = manager.get_csv_metadata()

        assert result is not None
        assert isinstance(result, CacheMetadata)
        assert result.locations_count == 2  # LOCATIONS_CSV has 2 rows
        assert result.policies_count == 3  # POLICIES_CSV has 3 rows
        # Should use the older mtime
        assert result.last_updated == datetime(
            2024, 1, 15, 12, 0, 0, tzinfo=timezone.utc
        )

    @pytest.mark.unit
    def test_with_only_locations_csv(self, tmp_path: Path) -> None:
        """get_csv_metadata works with only locations.csv."""
        manager = CacheManager(cache_dir=str(tmp_path))

        locations_path = tmp_path / "locations.csv"
        locations_path.write_text(LOCATIONS_CSV, encoding="utf-8")

        result = manager.get_csv_metadata()

        assert result is not None
        assert result.locations_count == 2
        assert result.policies_count == 0
        assert result.last_updated is not None

    @pytest.mark.unit
    def test_with_only_policies_csv(self, tmp_path: Path) -> None:
        """get_csv_metadata works with only policies.csv."""
        manager = CacheManager(cache_dir=str(tmp_path))

        policies_path = tmp_path / "policies.csv"
        policies_path.write_text(POLICIES_CSV, encoding="utf-8")

        result = manager.get_csv_metadata()

        assert result is not None
        assert result.locations_count == 0
        assert result.policies_count == 3
        assert result.last_updated is not None

    @pytest.mark.unit
    def test_handles_empty_csv_files(self, tmp_path: Path) -> None:
        """get_csv_metadata returns 0 counts for empty CSV files."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create empty files (header only)
        (tmp_path / "locations.csv").write_text("col1,col2\n", encoding="utf-8")
        (tmp_path / "policies.csv").write_text("col1,col2\n", encoding="utf-8")

        result = manager.get_csv_metadata()

        assert result is not None
        assert result.locations_count == 0
        assert result.policies_count == 0


class TestGetCacheAgeDays:
    """Tests for get_cache_age_days method."""

    @pytest.mark.unit
    def test_returns_none_when_no_cache(self, tmp_path: Path) -> None:
        """get_cache_age_days returns None when no CSV files exist."""
        manager = CacheManager(cache_dir=str(tmp_path))

        result = manager.get_cache_age_days()

        assert result is None

    @pytest.mark.unit
    def test_calculates_age_correctly(self, tmp_path: Path) -> None:
        """get_cache_age_days calculates correct number of days."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create a CSV file
        locations_path = tmp_path / "locations.csv"
        locations_path.write_text(LOCATIONS_CSV, encoding="utf-8")

        # Set mtime to 5 days ago
        five_days_ago = datetime.now(timezone.utc) - timedelta(days=5)
        os.utime(locations_path, (five_days_ago.timestamp(), five_days_ago.timestamp()))

        result = manager.get_cache_age_days()

        assert result is not None
        assert result == 5

    @pytest.mark.unit
    def test_returns_zero_for_fresh_cache(self, tmp_path: Path) -> None:
        """get_cache_age_days returns 0 for cache created today."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create a CSV file (default mtime is now)
        (tmp_path / "locations.csv").write_text(LOCATIONS_CSV, encoding="utf-8")

        result = manager.get_cache_age_days()

        assert result is not None
        assert result == 0


class TestIsCacheStale:
    """Tests for is_cache_stale method."""

    @pytest.mark.unit
    def test_returns_true_when_no_cache_exists(self, tmp_path: Path) -> None:
        """is_cache_stale returns True when no CSV files exist."""
        manager = CacheManager(cache_dir=str(tmp_path))

        result = manager.is_cache_stale()

        assert result is True

    @pytest.mark.unit
    def test_returns_true_when_exceeds_threshold(self, tmp_path: Path) -> None:
        """is_cache_stale returns True when cache is older than CSV_STALE_DAYS."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create a CSV file
        locations_path = tmp_path / "locations.csv"
        locations_path.write_text(LOCATIONS_CSV, encoding="utf-8")

        # Set mtime to CSV_STALE_DAYS + 1 days ago
        stale_time = datetime.now(timezone.utc) - timedelta(days=CSV_STALE_DAYS + 1)
        os.utime(locations_path, (stale_time.timestamp(), stale_time.timestamp()))

        result = manager.is_cache_stale()

        assert result is True

    @pytest.mark.unit
    def test_returns_false_for_fresh_cache(self, tmp_path: Path) -> None:
        """is_cache_stale returns False for cache within CSV_STALE_DAYS."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create a CSV file
        locations_path = tmp_path / "locations.csv"
        locations_path.write_text(LOCATIONS_CSV, encoding="utf-8")

        # Set mtime to 5 days ago (within threshold)
        fresh_time = datetime.now(timezone.utc) - timedelta(days=5)
        os.utime(locations_path, (fresh_time.timestamp(), fresh_time.timestamp()))

        result = manager.is_cache_stale()

        assert result is False

    @pytest.mark.unit
    def test_returns_false_at_exact_threshold(self, tmp_path: Path) -> None:
        """is_cache_stale returns False when cache is exactly CSV_STALE_DAYS old."""
        manager = CacheManager(cache_dir=str(tmp_path))

        # Create a CSV file
        locations_path = tmp_path / "locations.csv"
        locations_path.write_text(LOCATIONS_CSV, encoding="utf-8")

        # Set mtime to exactly CSV_STALE_DAYS ago
        threshold_time = datetime.now(timezone.utc) - timedelta(days=CSV_STALE_DAYS)
        os.utime(
            locations_path, (threshold_time.timestamp(), threshold_time.timestamp())
        )

        result = manager.is_cache_stale()

        # At exactly the threshold, it should not be stale (> not >=)
        assert result is False
