"""Cache utilities tests for the CLI."""

from __future__ import annotations
from datetime import UTC, datetime, timedelta
from pathlib import Path
import pytest
from orcheo_sdk.cli.cache import CacheEntry, CacheManager
from orcheo_sdk.cli.errors import CLIError
from orcheo_sdk.cli.http import ApiClient
from orcheo_sdk.cli.state import CLIState
from orcheo_sdk.cli.utils import load_with_cache


def test_cache_entry_is_stale() -> None:
    past_timestamp = datetime.now(tz=UTC) - timedelta(hours=2)
    entry = CacheEntry(
        payload={"key": "value"}, timestamp=past_timestamp, ttl=timedelta(hours=1)
    )
    assert entry.is_stale


def test_cache_entry_is_fresh() -> None:
    recent_timestamp = datetime.now(tz=UTC)
    entry = CacheEntry(
        payload={"key": "value"}, timestamp=recent_timestamp, ttl=timedelta(hours=1)
    )
    assert not entry.is_stale


def test_cache_manager_store_and_load(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    cache.store("test_key", {"data": "value"})
    entry = cache.load("test_key")
    assert entry is not None
    assert entry.payload == {"data": "value"}


def test_cache_manager_load_nonexistent(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    entry = cache.load("nonexistent")
    assert entry is None


def test_cache_manager_fetch_fresh_data(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    payload, from_cache, is_stale = cache.fetch("key", lambda: {"fresh": "data"})
    assert payload == {"fresh": "data"}
    assert not from_cache
    assert not is_stale


def test_cache_manager_fetch_on_error_uses_cache(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    cache.store("key", {"cached": "data"})

    def failing_loader() -> dict:
        raise CLIError("Network error")

    payload, from_cache, is_stale = cache.fetch("key", failing_loader)
    assert payload == {"cached": "data"}
    assert from_cache


def test_cache_manager_fetch_on_error_no_cache_raises(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))

    def failing_loader() -> dict:
        raise CLIError("Network error")

    with pytest.raises(CLIError):
        cache.fetch("key", failing_loader)


def test_cache_manager_load_or_raise_success(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    cache.store("key", {"data": "value"})
    payload = cache.load_or_raise("key")
    assert payload == {"data": "value"}


def test_cache_manager_load_or_raise_missing(tmp_path: Path) -> None:
    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    with pytest.raises(CLIError, match="not found"):
        cache.load_or_raise("missing")


def test_load_with_cache_offline_mode_with_cache(tmp_path: Path) -> None:
    from rich.console import Console

    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    cache.store("test_key", {"cached": "data"})

    from orcheo_sdk.cli.config import CLISettings

    settings = CLISettings(
        api_url="http://test.com", service_token=None, profile="test", offline=True
    )
    client = ApiClient(base_url="http://test.com", token=None)
    state = CLIState(settings=settings, client=client, cache=cache, console=Console())

    payload, from_cache, is_stale = load_with_cache(
        state,
        "test_key",
        lambda: {"fresh": "data"},
    )
    assert payload == {"cached": "data"}
    assert from_cache


def test_load_with_cache_offline_mode_error_uses_cache(tmp_path: Path) -> None:
    from rich.console import Console
    from orcheo_sdk.cli.config import CLISettings

    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))

    settings = CLISettings(
        api_url="http://test.com", service_token=None, profile="test", offline=True
    )
    client = ApiClient(base_url="http://test.com", token=None)
    state = CLIState(settings=settings, client=client, cache=cache, console=Console())

    def failing_loader() -> dict:
        cache.store("test_key", {"cached": "data"})
        raise CLIError("Network error")

    payload, from_cache, is_stale = load_with_cache(state, "test_key", failing_loader)
    assert payload == {"cached": "data"}
    assert from_cache
    assert not is_stale


def test_load_with_cache_online_mode_success(tmp_path: Path) -> None:
    from rich.console import Console

    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))

    from orcheo_sdk.cli.config import CLISettings

    settings = CLISettings(
        api_url="http://test.com", service_token=None, profile="test", offline=False
    )
    client = ApiClient(base_url="http://test.com", token=None)
    state = CLIState(settings=settings, client=client, cache=cache, console=Console())

    payload, from_cache, is_stale = load_with_cache(
        state,
        "test_key",
        lambda: {"fresh": "data"},
    )
    assert payload == {"fresh": "data"}
    assert not from_cache


def test_load_with_cache_online_mode_error_with_cache(tmp_path: Path) -> None:
    from rich.console import Console

    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))
    cache.store("test_key", {"cached": "data"})

    from orcheo_sdk.cli.config import CLISettings

    settings = CLISettings(
        api_url="http://test.com", service_token=None, profile="test", offline=False
    )
    client = ApiClient(base_url="http://test.com", token=None)
    state = CLIState(settings=settings, client=client, cache=cache, console=Console())

    def failing_loader() -> dict:
        raise CLIError("Network error")

    with pytest.raises(CLIError):
        load_with_cache(state, "test_key", failing_loader)


def test_load_with_cache_online_mode_error_no_cache(tmp_path: Path) -> None:
    from rich.console import Console

    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))

    from orcheo_sdk.cli.config import CLISettings

    settings = CLISettings(
        api_url="http://test.com", service_token=None, profile="test", offline=False
    )
    client = ApiClient(base_url="http://test.com", token=None)
    state = CLIState(settings=settings, client=client, cache=cache, console=Console())

    def failing_loader() -> dict:
        raise CLIError("Network error")

    with pytest.raises(CLIError):
        load_with_cache(state, "test_key", failing_loader)


def test_load_with_cache_offline_mode_without_cache(tmp_path: Path) -> None:
    """Test load_with_cache in offline mode when cache is missing."""
    from rich.console import Console
    from orcheo_sdk.cli.config import CLISettings

    cache_dir = tmp_path / "cache"
    cache = CacheManager(directory=cache_dir, ttl=timedelta(hours=1))

    settings = CLISettings(
        api_url="http://test.com", service_token=None, profile="test", offline=True
    )
    client = ApiClient(base_url="http://test.com", token=None)
    state = CLIState(settings=settings, client=client, cache=cache, console=Console())

    # In offline mode without cache, should try to load and get None
    # Then attempt to call loader which should not be called in true offline
    # But based on the code, it will try the loader anyway after cache miss
    def loader() -> dict:
        return {"fresh": "data"}

    payload, from_cache, is_stale = load_with_cache(state, "missing_key", loader)
    # When offline and no cache, it tries the loader
    assert payload == {"fresh": "data"}
    assert not from_cache
