"""Tests for the CloudKit client."""

import json
import pytest
from datetime import datetime
from unittest.mock import Mock, patch, MagicMock

from sportstime_parser.uploaders.cloudkit import (
    CloudKitClient,
    CloudKitRecord,
    CloudKitError,
    CloudKitAuthError,
    CloudKitRateLimitError,
    CloudKitServerError,
    RecordType,
    OperationResult,
    BatchResult,
)


class TestCloudKitRecord:
    """Tests for CloudKitRecord dataclass."""

    def test_create_record(self):
        """Test creating a CloudKitRecord."""
        record = CloudKitRecord(
            record_name="nba_2025_hou_okc_1021",
            record_type=RecordType.GAME,
            fields={
                "sport": "nba",
                "season": 2025,
            },
        )

        assert record.record_name == "nba_2025_hou_okc_1021"
        assert record.record_type == RecordType.GAME
        assert record.fields["sport"] == "nba"
        assert record.record_change_tag is None

    def test_to_cloudkit_dict(self):
        """Test converting to CloudKit API format."""
        record = CloudKitRecord(
            record_name="nba_2025_hou_okc_1021",
            record_type=RecordType.GAME,
            fields={
                "sport": "nba",
                "season": 2025,
            },
        )

        data = record.to_cloudkit_dict()

        assert data["recordName"] == "nba_2025_hou_okc_1021"
        assert data["recordType"] == "Game"
        assert "fields" in data
        assert "recordChangeTag" not in data

    def test_to_cloudkit_dict_with_change_tag(self):
        """Test converting with change tag for updates."""
        record = CloudKitRecord(
            record_name="nba_2025_hou_okc_1021",
            record_type=RecordType.GAME,
            fields={"sport": "nba"},
            record_change_tag="abc123",
        )

        data = record.to_cloudkit_dict()

        assert data["recordChangeTag"] == "abc123"

    def test_format_string_field(self):
        """Test formatting string fields."""
        record = CloudKitRecord(
            record_name="test",
            record_type=RecordType.GAME,
            fields={"name": "Test Name"},
        )

        data = record.to_cloudkit_dict()

        assert data["fields"]["name"]["value"] == "Test Name"
        assert data["fields"]["name"]["type"] == "STRING"

    def test_format_int_field(self):
        """Test formatting integer fields."""
        record = CloudKitRecord(
            record_name="test",
            record_type=RecordType.GAME,
            fields={"count": 42},
        )

        data = record.to_cloudkit_dict()

        assert data["fields"]["count"]["value"] == 42
        assert data["fields"]["count"]["type"] == "INT64"

    def test_format_float_field(self):
        """Test formatting float fields."""
        record = CloudKitRecord(
            record_name="test",
            record_type=RecordType.STADIUM,
            fields={"latitude": 35.4634},
        )

        data = record.to_cloudkit_dict()

        assert data["fields"]["latitude"]["value"] == 35.4634
        assert data["fields"]["latitude"]["type"] == "DOUBLE"

    def test_format_datetime_field(self):
        """Test formatting datetime fields."""
        dt = datetime(2025, 10, 21, 19, 0, 0)
        record = CloudKitRecord(
            record_name="test",
            record_type=RecordType.GAME,
            fields={"game_date": dt},
        )

        data = record.to_cloudkit_dict()

        expected_ms = int(dt.timestamp() * 1000)
        assert data["fields"]["game_date"]["value"] == expected_ms
        assert data["fields"]["game_date"]["type"] == "TIMESTAMP"

    def test_format_location_field(self):
        """Test formatting location fields."""
        record = CloudKitRecord(
            record_name="test",
            record_type=RecordType.STADIUM,
            fields={
                "location": {"latitude": 35.4634, "longitude": -97.5151},
            },
        )

        data = record.to_cloudkit_dict()

        assert data["fields"]["location"]["type"] == "LOCATION"
        assert data["fields"]["location"]["value"]["latitude"] == 35.4634
        assert data["fields"]["location"]["value"]["longitude"] == -97.5151

    def test_skip_none_fields(self):
        """Test that None fields are skipped."""
        record = CloudKitRecord(
            record_name="test",
            record_type=RecordType.GAME,
            fields={
                "sport": "nba",
                "score": None,  # Should be skipped
            },
        )

        data = record.to_cloudkit_dict()

        assert "sport" in data["fields"]
        assert "score" not in data["fields"]


class TestOperationResult:
    """Tests for OperationResult dataclass."""

    def test_successful_result(self):
        """Test creating a successful operation result."""
        result = OperationResult(
            record_name="test_record",
            success=True,
            record_change_tag="new_tag",
        )

        assert result.record_name == "test_record"
        assert result.success is True
        assert result.record_change_tag == "new_tag"
        assert result.error_code is None

    def test_failed_result(self):
        """Test creating a failed operation result."""
        result = OperationResult(
            record_name="test_record",
            success=False,
            error_code="SERVER_ERROR",
            error_message="Internal server error",
        )

        assert result.success is False
        assert result.error_code == "SERVER_ERROR"
        assert result.error_message == "Internal server error"


class TestBatchResult:
    """Tests for BatchResult dataclass."""

    def test_empty_batch_result(self):
        """Test empty batch result."""
        result = BatchResult()

        assert result.all_succeeded is True
        assert result.success_count == 0
        assert result.failure_count == 0

    def test_batch_with_successes(self):
        """Test batch with successful operations."""
        result = BatchResult()
        result.successful.append(OperationResult("rec1", True))
        result.successful.append(OperationResult("rec2", True))

        assert result.all_succeeded is True
        assert result.success_count == 2
        assert result.failure_count == 0

    def test_batch_with_failures(self):
        """Test batch with failed operations."""
        result = BatchResult()
        result.successful.append(OperationResult("rec1", True))
        result.failed.append(OperationResult("rec2", False, error_message="Error"))

        assert result.all_succeeded is False
        assert result.success_count == 1
        assert result.failure_count == 1


class TestCloudKitClient:
    """Tests for CloudKitClient."""

    def test_not_configured_without_credentials(self):
        """Test that client reports not configured without credentials."""
        with patch.dict("os.environ", {}, clear=True):
            client = CloudKitClient()
            assert client.is_configured is False

    def test_configured_with_credentials(self):
        """Test that client reports configured with credentials."""
        # Create a minimal mock for the private key
        mock_key = MagicMock()

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key_id",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                mock_load.return_value = mock_key
                client = CloudKitClient()
                assert client.is_configured is True

    def test_get_api_path(self):
        """Test API path construction."""
        client = CloudKitClient(
            container_id="iCloud.com.test.app",
            environment="development",
        )

        path = client._get_api_path("records/query")

        assert path == "/database/1/iCloud.com.test.app/development/public/records/query"

    @patch("sportstime_parser.uploaders.cloudkit.requests.Session")
    def test_fetch_records_query(self, mock_session_class):
        """Test fetching records with query."""
        mock_session = MagicMock()
        mock_session_class.return_value = mock_session

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "records": [
                {"recordName": "rec1", "recordType": "Game"},
                {"recordName": "rec2", "recordType": "Game"},
            ]
        }
        mock_session.request.return_value = mock_response

        # Setup client with mocked auth
        mock_key = MagicMock()
        mock_key.sign.return_value = b"signature"

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                with patch("sportstime_parser.uploaders.cloudkit.jwt.encode") as mock_jwt:
                    mock_load.return_value = mock_key
                    mock_jwt.return_value = "test_token"

                    client = CloudKitClient()
                    records = client.fetch_records(RecordType.GAME)

                    assert len(records) == 2
                    assert records[0]["recordName"] == "rec1"

    @patch("sportstime_parser.uploaders.cloudkit.requests.Session")
    def test_save_records_success(self, mock_session_class):
        """Test saving records successfully."""
        mock_session = MagicMock()
        mock_session_class.return_value = mock_session

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "records": [
                {"recordName": "rec1", "recordChangeTag": "tag1"},
                {"recordName": "rec2", "recordChangeTag": "tag2"},
            ]
        }
        mock_session.request.return_value = mock_response

        mock_key = MagicMock()
        mock_key.sign.return_value = b"signature"

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                with patch("sportstime_parser.uploaders.cloudkit.jwt.encode") as mock_jwt:
                    mock_load.return_value = mock_key
                    mock_jwt.return_value = "test_token"

                    client = CloudKitClient()

                    records = [
                        CloudKitRecord("rec1", RecordType.GAME, {"sport": "nba"}),
                        CloudKitRecord("rec2", RecordType.GAME, {"sport": "nba"}),
                    ]

                    result = client.save_records(records)

                    assert result.success_count == 2
                    assert result.failure_count == 0

    @patch("sportstime_parser.uploaders.cloudkit.requests.Session")
    def test_save_records_partial_failure(self, mock_session_class):
        """Test saving records with some failures."""
        mock_session = MagicMock()
        mock_session_class.return_value = mock_session

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {
            "records": [
                {"recordName": "rec1", "recordChangeTag": "tag1"},
                {"recordName": "rec2", "serverErrorCode": "QUOTA_EXCEEDED", "reason": "Quota exceeded"},
            ]
        }
        mock_session.request.return_value = mock_response

        mock_key = MagicMock()
        mock_key.sign.return_value = b"signature"

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                with patch("sportstime_parser.uploaders.cloudkit.jwt.encode") as mock_jwt:
                    mock_load.return_value = mock_key
                    mock_jwt.return_value = "test_token"

                    client = CloudKitClient()

                    records = [
                        CloudKitRecord("rec1", RecordType.GAME, {"sport": "nba"}),
                        CloudKitRecord("rec2", RecordType.GAME, {"sport": "nba"}),
                    ]

                    result = client.save_records(records)

                    assert result.success_count == 1
                    assert result.failure_count == 1
                    assert result.failed[0].error_code == "QUOTA_EXCEEDED"

    @patch("sportstime_parser.uploaders.cloudkit.requests.Session")
    def test_auth_error(self, mock_session_class):
        """Test handling authentication error."""
        mock_session = MagicMock()
        mock_session_class.return_value = mock_session

        mock_response = MagicMock()
        mock_response.status_code = 421
        mock_session.request.return_value = mock_response

        mock_key = MagicMock()
        mock_key.sign.return_value = b"signature"

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                with patch("sportstime_parser.uploaders.cloudkit.jwt.encode") as mock_jwt:
                    mock_load.return_value = mock_key
                    mock_jwt.return_value = "test_token"

                    client = CloudKitClient()

                    with pytest.raises(CloudKitAuthError):
                        client.fetch_records(RecordType.GAME)

    @patch("sportstime_parser.uploaders.cloudkit.requests.Session")
    def test_rate_limit_error(self, mock_session_class):
        """Test handling rate limit error."""
        mock_session = MagicMock()
        mock_session_class.return_value = mock_session

        mock_response = MagicMock()
        mock_response.status_code = 429
        mock_session.request.return_value = mock_response

        mock_key = MagicMock()
        mock_key.sign.return_value = b"signature"

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                with patch("sportstime_parser.uploaders.cloudkit.jwt.encode") as mock_jwt:
                    mock_load.return_value = mock_key
                    mock_jwt.return_value = "test_token"

                    client = CloudKitClient()

                    with pytest.raises(CloudKitRateLimitError):
                        client.fetch_records(RecordType.GAME)

    @patch("sportstime_parser.uploaders.cloudkit.requests.Session")
    def test_server_error(self, mock_session_class):
        """Test handling server error."""
        mock_session = MagicMock()
        mock_session_class.return_value = mock_session

        mock_response = MagicMock()
        mock_response.status_code = 503
        mock_session.request.return_value = mock_response

        mock_key = MagicMock()
        mock_key.sign.return_value = b"signature"

        with patch.dict("os.environ", {
            "CLOUDKIT_KEY_ID": "test_key",
            "CLOUDKIT_PRIVATE_KEY": "-----BEGIN EC PRIVATE KEY-----\ntest\n-----END EC PRIVATE KEY-----",
        }):
            with patch("sportstime_parser.uploaders.cloudkit.serialization.load_pem_private_key") as mock_load:
                with patch("sportstime_parser.uploaders.cloudkit.jwt.encode") as mock_jwt:
                    mock_load.return_value = mock_key
                    mock_jwt.return_value = "test_token"

                    client = CloudKitClient()

                    with pytest.raises(CloudKitServerError):
                        client.fetch_records(RecordType.GAME)


class TestRecordType:
    """Tests for RecordType enum."""

    def test_record_type_values(self):
        """Test that record type values match CloudKit schema."""
        assert RecordType.GAME.value == "Game"
        assert RecordType.TEAM.value == "Team"
        assert RecordType.STADIUM.value == "Stadium"
        assert RecordType.TEAM_ALIAS.value == "TeamAlias"
        assert RecordType.STADIUM_ALIAS.value == "StadiumAlias"
