"""Comprehensive unit tests for API Gateway adapters."""

from unittest.mock import Mock, patch

import botocore.exceptions
import pytest

from augint_billing_lib.adapters.apigw_admin import ApiGwAdmin
from augint_billing_lib.adapters.apigw_usage import ApiGwUsage


@pytest.mark.unit
class TestApiGwAdminInit:
    """Test ApiGwAdmin initialization."""

    def test_init(self):
        """Test ApiGwAdmin initialization."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")
        assert admin.apigw == mock_client
        assert admin.free_plan_id == "FREE_10K"
        assert admin.metered_plan_id == "METERED"


@pytest.mark.unit
class TestApiGwAdminMethods:
    """Test ApiGwAdmin methods."""

    def test_remove_from_plan_success(self):
        """Test _remove_from_plan removes key from usage plan."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock paginator
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "key_id_123", "value": "key_value_abc"}]}
        ]

        admin._remove_from_plan("FREE_10K", "key_id_123")

        mock_client.get_paginator.assert_called_once_with("get_usage_plan_keys")
        mock_paginator.paginate.assert_called_once_with(
            usagePlanId="FREE_10K", PaginationConfig={"PageSize": 500}
        )
        mock_client.delete_usage_plan_key.assert_called_once_with(
            usagePlanId="FREE_10K", keyId="key_id_123"
        )

    def test_remove_from_plan_key_not_found(self):
        """Test _remove_from_plan when key not in plan."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock empty plan
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [{"items": []}]

        admin._remove_from_plan("FREE_10K", "key_not_there")

        mock_client.get_paginator.assert_called_once_with("get_usage_plan_keys")
        mock_paginator.paginate.assert_called_once_with(
            usagePlanId="FREE_10K", PaginationConfig={"PageSize": 500}
        )
        mock_client.delete_usage_plan_key.assert_not_called()

    def test_remove_from_plan_multiple_keys(self):
        """Test _remove_from_plan finds correct key among many."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock plan with multiple keys
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {
                "items": [
                    {"id": "id_1", "value": "val1"},
                    {"id": "id_2", "value": "val2"},
                    {"id": "id_3", "value": "val3"},
                ]
            }
        ]

        admin._remove_from_plan("FREE_10K", "id_2")

        mock_client.get_paginator.assert_called_once_with("get_usage_plan_keys")
        mock_paginator.paginate.assert_called_once_with(
            usagePlanId="FREE_10K", PaginationConfig={"PageSize": 500}
        )
        mock_client.delete_usage_plan_key.assert_called_once_with(
            usagePlanId="FREE_10K", keyId="id_2"
        )

    def test_remove_from_plan_pagination(self):
        """Test _remove_from_plan handles pagination."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock paginated responses
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "id_1", "value": "val1"}]},
            {"items": [{"id": "id_2", "value": "val2"}]},
        ]

        admin._remove_from_plan("FREE_10K", "id_2")

        mock_client.get_paginator.assert_called_once_with("get_usage_plan_keys")
        mock_paginator.paginate.assert_called_once_with(
            usagePlanId="FREE_10K", PaginationConfig={"PageSize": 500}
        )
        mock_client.delete_usage_plan_key.assert_called_once_with(
            usagePlanId="FREE_10K", keyId="id_2"
        )

    def test_move_key_to_plan_free(self):
        """Test move_key_to_plan moves to free plan."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock paginator to find the key
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "key_123", "value": "some_value"}]}
        ]

        # Mock remove and create operations
        with patch.object(admin, "_remove_from_plan") as mock_remove:
            admin.move_key_to_plan("key_123", "FREE_10K")

            # Should remove from metered and add to free
            mock_remove.assert_called_once_with("METERED", "key_123")
            mock_client.create_usage_plan_key.assert_called_once_with(
                usagePlanId="FREE_10K", keyId="key_123", keyType="API_KEY"
            )

    def test_move_key_to_plan_metered(self):
        """Test move_key_to_plan moves to metered plan."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock paginator to find the key
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "key_456", "value": "some_value"}]}
        ]

        with patch.object(admin, "_remove_from_plan") as mock_remove:
            admin.move_key_to_plan("key_456", "METERED")

            # Should remove from free and add to metered
            mock_remove.assert_called_once_with("FREE_10K", "key_456")
            mock_client.create_usage_plan_key.assert_called_once_with(
                usagePlanId="METERED", keyId="key_456", keyType="API_KEY"
            )


@pytest.mark.unit
class TestApiGwUsageInit:
    """Test ApiGwUsage initialization."""

    def test_init(self):
        """Test ApiGwUsage initialization."""
        mock_client = Mock()
        usage = ApiGwUsage(mock_client)
        assert usage.apigw == mock_client


@pytest.mark.unit
class TestApiGwUsageMethods:
    """Test ApiGwUsage methods."""

    def test_get_usage_with_data(self):
        """Test get_usage returns sum of usage data."""
        from datetime import UTC, datetime

        mock_client = Mock()
        usage = ApiGwUsage(mock_client)

        # Mock response with usage data
        mock_client.get_usage.return_value = {
            "values": {"prod": [{"day1": "100"}, {"day2": "200"}], "test": [{"day1": "50"}]}
        }

        since = datetime(2024, 1, 1, tzinfo=UTC)
        until = datetime(2024, 1, 31, tzinfo=UTC)

        result = usage.get_usage("plan_123", "key_123", since, until)

        assert result == 350  # 100 + 200 + 50
        mock_client.get_usage.assert_called_once_with(
            usagePlanId="plan_123", keyId="key_123", startDate="2024-01-01", endDate="2024-01-31"
        )

    def test_get_usage_no_values(self):
        """Test get_usage when no values key in response."""
        from datetime import UTC, datetime

        mock_client = Mock()
        usage = ApiGwUsage(mock_client)

        # Mock response without values
        mock_client.get_usage.return_value = {}

        since = datetime(2024, 1, 1, tzinfo=UTC)
        until = datetime(2024, 1, 31, tzinfo=UTC)

        result = usage.get_usage("plan_123", "key_123", since, until)

        assert result == 0

    def test_get_usage_handles_non_numeric(self):
        """Test get_usage handles non-numeric values gracefully."""
        from datetime import UTC, datetime

        mock_client = Mock()
        usage = ApiGwUsage(mock_client)

        # Mock response with mixed values
        mock_client.get_usage.return_value = {
            "values": {
                "prod": [
                    {"day1": "100"},
                    {"day2": "invalid"},  # Will be skipped
                    {"day3": "50"},
                ]
            }
        }

        since = datetime(2024, 1, 1, tzinfo=UTC)
        until = datetime(2024, 1, 31, tzinfo=UTC)

        result = usage.get_usage("plan_123", "key_123", since, until)

        assert result == 150  # 100 + 50, "invalid" is skipped


@pytest.mark.unit
class TestApiGwAdminRetry:
    """Test retry behavior for ApiGwAdmin."""

    def test_remove_from_plan_retries_on_error(self):
        """Test _remove_from_plan retries on ClientError."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Create a paginator that fails first, then succeeds
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator

        # First call to paginate fails, second succeeds
        mock_paginator.paginate.side_effect = [
            botocore.exceptions.ClientError(
                {"Error": {"Code": "TooManyRequestsException"}}, "GetUsagePlanKeys"
            ),
            [{"items": [{"id": "key_id", "value": "key_val"}]}],
        ]

        admin._remove_from_plan("FREE_10K", "key_id")

        assert mock_paginator.paginate.call_count == 2
        mock_client.delete_usage_plan_key.assert_called_once()

    def test_move_key_to_plan_retries_on_error(self):
        """Test move_key_to_plan retries on ClientError."""
        mock_client = Mock()
        admin = ApiGwAdmin(mock_client, "FREE_10K", "METERED")

        # Mock paginator to find the key
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "key_123", "value": "some_value"}]}
        ]

        # First create fails, second succeeds
        mock_client.create_usage_plan_key.side_effect = [
            botocore.exceptions.ClientError(
                {"Error": {"Code": "TooManyRequestsException"}}, "CreateUsagePlanKey"
            ),
            {"keyId": "key_123"},
        ]

        with patch.object(admin, "_remove_from_plan"):
            admin.move_key_to_plan("key_123", "free")

        assert mock_client.create_usage_plan_key.call_count == 2


@pytest.mark.unit
class TestApiGwUsageRetry:
    """Test retry behavior for ApiGwUsage."""

    def test_get_usage_retries_on_error(self):
        """Test get_usage retries on ClientError."""
        from datetime import UTC, datetime

        mock_client = Mock()
        usage = ApiGwUsage(mock_client)

        # First call fails, second succeeds
        mock_client.get_usage.side_effect = [
            botocore.exceptions.ClientError(
                {"Error": {"Code": "TooManyRequestsException"}}, "GetUsage"
            ),
            {"values": {"prod": [{"day1": "100"}]}},
        ]

        since = datetime(2024, 1, 1, tzinfo=UTC)
        until = datetime(2024, 1, 31, tzinfo=UTC)

        result = usage.get_usage("plan_123", "key_123", since, until)

        assert result == 100
        assert mock_client.get_usage.call_count == 2
