# Copyright 2026 4f4d
# Licensed under the Apache License, Version 2.0

"""Tests for f1radio.client."""

import pytest
from unittest.mock import patch, MagicMock

from f1radio.client import OpenF1Client, _RateLimiter, RequestStats, stats


class TestRateLimiter:
    def test_creates_without_error(self):
        limiter = _RateLimiter(max_rps=10, max_rpm=100)
        limiter.wait()  # Should not raise


class TestRequestStats:
    def test_tracks_api_calls(self):
        s = RequestStats()
        s.log_api_call()
        s.log_api_call()
        assert s.api_calls == 2

    def test_tracks_cache_hits(self):
        s = RequestStats()
        s.log_cache_hit()
        assert s.cache_hits == 1

    def test_tracks_rate_limited(self):
        s = RequestStats()
        s.log_rate_limited()
        assert s.rate_limited == 1

    def test_summary(self):
        s = RequestStats()
        s.log_api_call()
        summary = s.summary()
        assert "API calls: 1" in summary

    def test_reset(self):
        s = RequestStats()
        s.log_api_call()
        s.log_error()
        s.reset()
        assert s.api_calls == 0
        assert s.errors == 0


class TestOpenF1Client:
    @patch("f1radio.client.requests.Session")
    def test_get_sessions(self, mock_session_cls):
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [{"session_key": 9158}]
        mock_response.headers = {}
        mock_response.content = b'[{"session_key": 9158}]'

        mock_session = MagicMock()
        mock_session.get.return_value = mock_response
        mock_session_cls.return_value = mock_session

        client = OpenF1Client()
        client._session = mock_session

        result = client.get_sessions(year=2023)
        assert len(result) == 1
        assert result[0]["session_key"] == 9158

    @patch("f1radio.client.requests.Session")
    def test_404_returns_empty(self, mock_session_cls):
        mock_response = MagicMock()
        mock_response.status_code = 404
        mock_response.text = "Not found"
        mock_response.headers = {}
        mock_response.content = b"Not found"

        mock_session = MagicMock()
        mock_session.get.return_value = mock_response
        mock_session_cls.return_value = mock_session

        client = OpenF1Client()
        client._session = mock_session

        result = client.get_team_radio(session_key=99999)
        assert result == []

    @patch("f1radio.client.requests.Session")
    def test_get_team_radio(self, mock_session_cls):
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = [
            {"driver_number": 1, "recording_url": "https://example.com/test.mp3"}
        ]
        mock_response.headers = {}
        mock_response.content = b'[{"driver_number": 1}]'

        mock_session = MagicMock()
        mock_session.get.return_value = mock_response
        mock_session_cls.return_value = mock_session

        client = OpenF1Client()
        client._session = mock_session

        result = client.get_team_radio(session_key=9158)
        assert len(result) == 1
        assert result[0]["driver_number"] == 1

    @patch("f1radio.client.requests.Session")
    def test_etag_304_returns_cached(self, mock_session_cls):
        """Test that ETag conditional requests return cached data on 304."""
        mock_response_200 = MagicMock()
        mock_response_200.status_code = 200
        mock_response_200.json.return_value = [{"data": "test"}]
        mock_response_200.headers = {"ETag": '"abc123"', "Last-Modified": "Thu, 01 Jan 2024"}
        mock_response_200.content = b'[{"data": "test"}]'

        mock_response_304 = MagicMock()
        mock_response_304.status_code = 304
        mock_response_304.headers = {}
        mock_response_304.content = b""

        mock_session = MagicMock()
        mock_session.get.side_effect = [mock_response_200, mock_response_304]
        mock_session_cls.return_value = mock_session

        client = OpenF1Client()
        client._session = mock_session

        # First call — 200, stores ETag
        result1 = client.get_drivers(session_key=9158)
        assert result1 == [{"data": "test"}]

        # Second call — 304, returns cached
        result2 = client.get_drivers(session_key=9158)
        assert result2 == [{"data": "test"}]

    @patch("f1radio.client.requests.Session")
    def test_circuit_breaker_on_429(self, mock_session_cls):
        """Test that 3 consecutive 429s triggers circuit breaker."""
        mock_response = MagicMock()
        mock_response.status_code = 429
        mock_response.headers = {}
        mock_response.text = "Too many requests"
        mock_response.content = b"Too many requests"

        mock_session = MagicMock()
        mock_session.get.return_value = mock_response
        mock_session_cls.return_value = mock_session

        client = OpenF1Client(max_retries=5)  # More retries than circuit breaker limit
        client._session = mock_session

        result = client.get_drivers(session_key=9158)
        # Should abort and return empty (not exhaust all 5 retries)
        assert result == []

    @patch("f1radio.client.requests.Session")
    def test_download_file(self, mock_session_cls, tmp_path):
        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.iter_content.return_value = [b"fake mp3 data"]

        mock_session = MagicMock()
        mock_session.get.return_value = mock_response
        mock_session_cls.return_value = mock_session

        client = OpenF1Client()
        client._session = mock_session

        dest = str(tmp_path / "test.mp3")
        result = client.download_file("https://example.com/test.mp3", dest)
        assert result is True

    def test_context_manager(self):
        with OpenF1Client() as client:
            assert client is not None

    def test_global_stats_accessible(self):
        """Test that the global stats object is accessible."""
        assert hasattr(stats, "api_calls")
        assert hasattr(stats, "summary")
