"""
Tests for CLI State File Reader and related functionality.
"""

import pytest
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch, PropertyMock

from agent_orchestrator.tracking import (
    RateLimitState,
    SessionState,
    CLIStateSnapshot,
    WindowType,
    WaitingState,
    CLIStateReader,
    ClaudeStateReader,
    CodexStateReader,
    GeminiStateReader,
    get_reader_for_agent,
    get_all_readers,
    register_reader,
    RateLimitMonitor,
    RateLimitAlert,
    get_rate_limit_monitor,
    set_rate_limit_monitor,
)


class TestRateLimitState:
    """Tests for RateLimitState dataclass."""

    def test_create_rate_limit_state(self):
        """Test creating a rate limit state."""
        reset_time = datetime.now() + timedelta(hours=1)
        state = RateLimitState(
            requests_used=10,
            requests_limit=100,
            reset_at=reset_time,
            window_type=WindowType.FIVE_HOUR,
        )
        assert state.requests_used == 10
        assert state.requests_limit == 100
        assert state.remaining == 90
        assert state.window_type == WindowType.FIVE_HOUR

    def test_usage_percentage(self):
        """Test usage percentage calculation."""
        state = RateLimitState(
            requests_used=50,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.DAILY,
        )
        assert state.percentage_used == 50.0

    def test_usage_percentage_zero_limit(self):
        """Test usage percentage with zero limit."""
        state = RateLimitState(
            requests_used=0,
            requests_limit=0,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.DAILY,
        )
        assert state.percentage_used == 0.0

    def test_is_exhausted(self):
        """Test is_exhausted property."""
        state = RateLimitState(
            requests_used=100,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.DAILY,
        )
        assert state.is_exhausted is True

        state2 = RateLimitState(
            requests_used=50,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.DAILY,
        )
        assert state2.is_exhausted is False

    def test_to_dict(self):
        """Test to_dict serialization."""
        reset_time = datetime.now() + timedelta(hours=1)
        state = RateLimitState(
            requests_used=10,
            requests_limit=100,
            reset_at=reset_time,
            window_type=WindowType.DAILY,
        )
        data = state.to_dict()
        assert data["requests_used"] == 10
        assert data["requests_limit"] == 100
        assert data["remaining"] == 90
        assert data["window_type"] == "daily"


class TestSessionState:
    """Tests for SessionState dataclass."""

    def test_create_session_state(self):
        """Test creating a session state."""
        now = datetime.now()
        state = SessionState(
            session_id="test-session",
            started_at=now,
            last_activity_at=now,
            is_active=True,
            waiting_for_input=True,
            waiting_state=WaitingState.APPROVAL,
            input_prompt="Continue? [y/n]",
        )
        assert state.session_id == "test-session"
        assert state.waiting_for_input is True
        assert state.waiting_state == WaitingState.APPROVAL

    def test_session_duration(self):
        """Test session duration calculation."""
        start = datetime.now() - timedelta(hours=1)
        last_activity = datetime.now()
        state = SessionState(
            session_id="test",
            started_at=start,
            last_activity_at=last_activity,
            is_active=True,
            waiting_for_input=False,
        )
        duration = state.duration_seconds
        assert 3500 < duration < 3700  # ~1 hour

    def test_to_dict(self):
        """Test to_dict serialization."""
        now = datetime.now()
        state = SessionState(
            session_id="test-session",
            started_at=now,
            last_activity_at=now,
            is_active=True,
            waiting_for_input=True,
            waiting_state=WaitingState.CLARIFICATION,
            input_prompt="Enter value:",
        )
        data = state.to_dict()
        assert data["session_id"] == "test-session"
        assert data["waiting_for_input"] is True
        assert data["waiting_state"] == "clarification"


class TestCLIStateSnapshot:
    """Tests for CLIStateSnapshot dataclass."""

    def test_create_snapshot(self):
        """Test creating a CLI state snapshot."""
        snapshot = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
        )
        assert snapshot.agent_type == "claude"
        assert snapshot.agent_id == "claude-1"
        assert snapshot.is_installed is True

    def test_snapshot_with_rate_limit(self):
        """Test snapshot with rate limit state."""
        rate_limit = RateLimitState(
            requests_used=10,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.FIVE_HOUR,
        )
        snapshot = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=rate_limit,
        )
        assert snapshot.rate_limit is not None
        assert snapshot.rate_limit.requests_used == 10

    def test_to_dict(self):
        """Test to_dict serialization."""
        snapshot = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
        )
        data = snapshot.to_dict()
        assert data["agent_type"] == "claude"
        assert data["agent_id"] == "claude-1"
        assert data["is_installed"] is True

    def test_is_available(self):
        """Test is_available property."""
        snapshot = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
        )
        assert snapshot.is_available is True

        # Not installed
        snapshot2 = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=False,
            state_dir_exists=False,
        )
        assert snapshot2.is_available is False


class TestClaudeStateReader:
    """Tests for ClaudeStateReader."""

    def test_reader_initialization(self):
        """Test reader initialization."""
        reader = ClaudeStateReader()
        assert reader.CLI_TYPE == "claude"

    def test_exists_check(self):
        """Test exists method."""
        reader = ClaudeStateReader()
        with patch('pathlib.Path.exists', return_value=True):
            assert reader.exists() is True

    def test_get_snapshot_no_state_dir(self):
        """Test get_snapshot when no state directory."""
        reader = ClaudeStateReader()
        with patch('pathlib.Path.exists', return_value=False):
            snapshot = reader.get_snapshot()
            assert snapshot.agent_type == "claude"
            assert snapshot.state_dir_exists is False


class TestCodexStateReader:
    """Tests for CodexStateReader."""

    def test_reader_initialization(self):
        """Test reader initialization."""
        reader = CodexStateReader()
        assert reader.CLI_TYPE == "codex"


class TestGeminiStateReader:
    """Tests for GeminiStateReader."""

    def test_reader_initialization(self):
        """Test reader initialization."""
        reader = GeminiStateReader()
        assert reader.CLI_TYPE == "gemini"


class TestReaderRegistry:
    """Tests for reader registry functions."""

    def test_get_reader_for_agent(self):
        """Test getting reader for agent type."""
        reader = get_reader_for_agent("claude")
        assert reader is not None
        assert isinstance(reader, ClaudeStateReader)

        reader = get_reader_for_agent("codex")
        assert isinstance(reader, CodexStateReader)

        reader = get_reader_for_agent("gemini")
        assert isinstance(reader, GeminiStateReader)

    def test_get_reader_unknown_agent(self):
        """Test getting reader for unknown agent."""
        reader = get_reader_for_agent("unknown_agent")
        assert reader is None

    def test_get_all_readers(self):
        """Test getting all readers."""
        readers = get_all_readers()
        assert "claude" in readers
        assert "codex" in readers
        assert "gemini" in readers

    def test_register_custom_reader(self):
        """Test registering a custom reader."""
        # Create a mock class that will be instantiated by get_reader_for_agent
        mock_reader_instance = MagicMock(spec=CLIStateReader)
        mock_reader_instance.CLI_TYPE = "custom"
        mock_reader_class = MagicMock(return_value=mock_reader_instance)

        register_reader("custom", mock_reader_class)
        reader = get_reader_for_agent("custom")
        # The registry stores the class, get_reader_for_agent instantiates it
        assert reader is mock_reader_instance
        mock_reader_class.assert_called_once()


class TestRateLimitMonitor:
    """Tests for RateLimitMonitor."""

    def setup_method(self):
        """Reset monitor before each test."""
        set_rate_limit_monitor(None)

    def test_monitor_initialization(self):
        """Test monitor initialization."""
        monitor = RateLimitMonitor()
        assert monitor.warning_threshold == 70
        assert monitor.critical_threshold == 90

    def test_custom_thresholds(self):
        """Test custom threshold configuration."""
        monitor = RateLimitMonitor(warning_threshold=60, critical_threshold=80)
        assert monitor.warning_threshold == 60
        assert monitor.critical_threshold == 80

    def test_check_agent_no_rate_limit(self):
        """Test checking agent with no rate limit data."""
        reader = MagicMock()
        reader.get_snapshot.return_value = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=None,
        )

        monitor = RateLimitMonitor(readers={"claude": reader})
        alert = monitor.check_agent("claude")
        assert alert is None

    def test_check_agent_normal_usage(self):
        """Test checking agent with normal usage."""
        rate_limit = RateLimitState(
            requests_used=30,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.FIVE_HOUR,
        )
        reader = MagicMock()
        reader.get_snapshot.return_value = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=rate_limit,
        )

        monitor = RateLimitMonitor(readers={"claude": reader})
        alert = monitor.check_agent("claude")
        assert alert is None

    def test_check_agent_warning_level(self):
        """Test checking agent at warning level."""
        rate_limit = RateLimitState(
            requests_used=75,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.FIVE_HOUR,
        )
        reader = MagicMock()
        reader.get_snapshot.return_value = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=rate_limit,
        )

        monitor = RateLimitMonitor(readers={"claude": reader})
        alert = monitor.check_agent("claude")
        assert alert is not None
        assert alert.level == "warning"

    def test_check_agent_critical_level(self):
        """Test checking agent at critical level."""
        rate_limit = RateLimitState(
            requests_used=95,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.FIVE_HOUR,
        )
        reader = MagicMock()
        reader.get_snapshot.return_value = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=rate_limit,
        )

        monitor = RateLimitMonitor(readers={"claude": reader})
        alert = monitor.check_agent("claude")
        assert alert is not None
        assert alert.level == "critical"

    def test_callback_registration(self):
        """Test callback registration and firing."""
        callback_called = []

        def callback(alert):
            callback_called.append(alert)

        rate_limit = RateLimitState(
            requests_used=95,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.FIVE_HOUR,
        )
        reader = MagicMock()
        reader.get_snapshot.return_value = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=rate_limit,
        )

        monitor = RateLimitMonitor(readers={"claude": reader})
        monitor.on_alert(callback)
        monitor.check_agent("claude")

        assert len(callback_called) == 1
        assert callback_called[0].level == "critical"

    def test_get_recent_alerts(self):
        """Test getting recent alerts."""
        rate_limit = RateLimitState(
            requests_used=95,
            requests_limit=100,
            reset_at=datetime.now() + timedelta(hours=1),
            window_type=WindowType.FIVE_HOUR,
        )
        reader = MagicMock()
        reader.get_snapshot.return_value = CLIStateSnapshot(
            agent_type="claude",
            agent_id="claude-1",
            is_installed=True,
            state_dir_exists=True,
            rate_limit=rate_limit,
        )

        monitor = RateLimitMonitor(readers={"claude": reader})
        monitor.check_agent("claude")

        alerts = monitor.get_recent_alerts()
        assert len(alerts) == 1
        assert alerts[0].agent_id == "claude"

    def test_global_monitor_instance(self):
        """Test global monitor instance management."""
        monitor1 = get_rate_limit_monitor()
        monitor2 = get_rate_limit_monitor()
        assert monitor1 is monitor2

        custom_monitor = RateLimitMonitor()
        set_rate_limit_monitor(custom_monitor)
        assert get_rate_limit_monitor() is custom_monitor


class TestWindowType:
    """Tests for WindowType enum."""

    def test_window_types(self):
        """Test window type values."""
        assert WindowType.FIVE_HOUR.value == "5_hour"
        assert WindowType.THREE_HOUR.value == "3_hour"
        assert WindowType.DAILY.value == "daily"
        assert WindowType.WEEKLY.value == "weekly"
        assert WindowType.UNLIMITED.value == "unlimited"


class TestWaitingState:
    """Tests for WaitingState enum."""

    def test_waiting_states(self):
        """Test waiting state values."""
        assert WaitingState.IDLE.value == "idle"
        assert WaitingState.APPROVAL.value == "approval"
        assert WaitingState.CLARIFICATION.value == "clarification"
        assert WaitingState.TOOL_USE.value == "tool_use"
        assert WaitingState.CONTINUE.value == "continue"
        assert WaitingState.CUSTOM.value == "custom"
