"""Integration tests for API Gateway adapters working together."""

import contextlib
from datetime import UTC, datetime, timedelta
from unittest.mock import Mock, call, patch

import pytest
from botocore.exceptions import ClientError

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


@pytest.mark.integration
class TestApiGatewayIntegration:
    """Test API Gateway admin and usage adapters working together."""

    @pytest.fixture
    def mock_apigw_client(self):
        """Create a mock API Gateway client."""
        with patch("boto3.client") as mock_client:
            mock_apigw = Mock()
            mock_client.return_value = mock_apigw
            yield mock_apigw

    @pytest.fixture
    def adapters(self, mock_apigw_client):
        """Create both admin and usage adapters."""
        admin = ApiGwAdmin(mock_apigw_client, "FREE_10K", "METERED")
        usage = ApiGwUsage(mock_apigw_client)
        return admin, usage, mock_apigw_client

    def test_move_key_and_track_usage(self, adapters):
        """Test moving a key between plans and tracking its usage."""
        admin, usage, mock_client = adapters
        api_key = "test_key_move"

        # Setup paginator for finding keys
        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator

        # Key starts in FREE plan
        mock_paginator.paginate.return_value = [{"items": [{"id": "key_id_123", "value": api_key}]}]

        # Move key from FREE to METERED
        admin.move_key_to_plan("key_id_123", "METERED")

        # Verify removal from FREE plan
        mock_client.delete_usage_plan_key.assert_called_with(
            usagePlanId="FREE_10K", keyId="key_id_123"
        )

        # Verify addition to METERED plan
        mock_client.create_usage_plan_key.assert_called_with(
            usagePlanId="METERED", keyId="key_id_123", keyType="API_KEY"
        )

        # Now track usage for the moved key
        mock_client.get_usage_plan_key.return_value = {"id": "key_id_123", "value": api_key}

        mock_client.get_usage.return_value = {
            "usagePlanId": "METERED",
            "values": {
                "Production": [
                    {"2025-08-20": 1500}  # 1500 requests
                ]
            },
        }

        now = datetime.now(UTC)
        hour_ago = now - timedelta(hours=1)

        usage_count = usage.get_usage("METERED", api_key, hour_ago, now)
        assert usage_count == 1500

    def test_bulk_key_migration(self, adapters):
        """Test migrating multiple keys between plans."""
        admin, usage, mock_client = adapters

        # Setup multiple keys to migrate
        keys_to_migrate = [
            ("key_bulk_1", "key_id_1"),
            ("key_bulk_2", "key_id_2"),
            ("key_bulk_3", "key_id_3"),
        ]

        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator

        # Setup responses for each key
        for key_value, key_id in keys_to_migrate:
            mock_paginator.paginate.return_value = [{"items": [{"id": key_id, "value": key_value}]}]

            # Move each key (pass the ID, not the value)
            admin.move_key_to_plan(key_id, "METERED")

        # Verify all keys were moved
        assert mock_client.delete_usage_plan_key.call_count == 3
        assert mock_client.create_usage_plan_key.call_count == 3

        # Verify correct parameters for each move
        delete_calls = mock_client.delete_usage_plan_key.call_args_list
        create_calls = mock_client.create_usage_plan_key.call_args_list

        for i, (_key_value, key_id) in enumerate(keys_to_migrate):
            assert delete_calls[i] == call(usagePlanId="FREE_10K", keyId=key_id)
            assert create_calls[i] == call(usagePlanId="METERED", keyId=key_id, keyType="API_KEY")

    def test_usage_collection_for_multiple_plans(self, adapters):
        """Test collecting usage across different plans."""
        admin, usage, mock_client = adapters

        # Setup usage data for different plans
        test_cases = [
            ("FREE_10K", "key_free", 500),
            ("METERED", "key_metered_1", 1500),
            ("METERED", "key_metered_2", 2500),
        ]

        for plan_id, api_key, expected_usage in test_cases:
            mock_client.get_usage_plan_key.return_value = {"id": f"id_{api_key}", "value": api_key}

            mock_client.get_usage.return_value = {
                "usagePlanId": plan_id,
                "values": {"Production": [{"2025-08-20": expected_usage}]},
            }

            now = datetime.now(UTC)
            hour_ago = now - timedelta(hours=1)

            usage_count = usage.get_usage(plan_id, api_key, hour_ago, now)
            assert usage_count == expected_usage

    def test_error_handling_during_migration(self, adapters):
        """Test error handling during key migration."""
        admin, usage, mock_client = adapters

        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator

        # Test 1: Key not found in any plan
        mock_paginator.paginate.return_value = [
            {"items": []}  # Empty results
        ]

        # Should handle gracefully (implementation may vary)
        admin.move_key_to_plan("nonexistent_key", "METERED")

        # Test 2: Delete fails with rate limiting
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "key_id_rate", "value": "key_rate"}]}
        ]

        mock_client.delete_usage_plan_key.side_effect = ClientError(
            {"Error": {"Code": "TooManyRequestsException"}}, "DeleteUsagePlanKey"
        )

        with pytest.raises(ClientError):
            admin.move_key_to_plan("key_id_rate", "METERED")

        # Test 3: Create fails with conflict
        mock_client.delete_usage_plan_key.side_effect = None  # Reset
        mock_client.create_usage_plan_key.side_effect = ClientError(
            {"Error": {"Code": "ConflictException"}}, "CreateUsagePlanKey"
        )

        with pytest.raises(ClientError):
            admin.move_key_to_plan("key_id_conflict", "METERED")

    def test_usage_with_pagination(self, adapters):
        """Test usage collection with paginated results."""
        admin, usage, mock_client = adapters

        # Mock paginated usage response
        mock_client.get_usage_plan_key.return_value = {
            "id": "key_id_paginated",
            "value": "key_paginated",
        }

        # First page
        mock_client.get_usage.return_value = {
            "usagePlanId": "METERED",
            "values": {
                "Production": [
                    {"2025-08-18": 500},  # Day 1: 500
                    {"2025-08-19": 700},  # Day 2: 700
                    {"2025-08-20": 800},  # Day 3: 800
                ]
            },
            "position": "page1_token",
        }

        now = datetime.now(UTC)
        three_days_ago = now - timedelta(days=3)

        total_usage = usage.get_usage("METERED", "key_paginated", three_days_ago, now)

        # Should sum all usage
        assert total_usage == 2000  # 500 + 700 + 800

    def test_concurrent_key_operations(self, adapters):
        """Test handling concurrent operations on the same key."""
        admin, usage, mock_client = adapters

        import threading
        from concurrent.futures import ThreadPoolExecutor

        api_key = "key_concurrent"

        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator
        mock_paginator.paginate.return_value = [
            {"items": [{"id": "key_id_concurrent", "value": api_key}]}
        ]

        # Track operation order
        operations = []
        lock = threading.Lock()

        def track_delete(**kwargs):
            with lock:
                operations.append(("delete", kwargs["usagePlanId"]))

        def track_create(**kwargs):
            with lock:
                operations.append(("create", kwargs["usagePlanId"]))

        mock_client.delete_usage_plan_key.side_effect = track_delete
        mock_client.create_usage_plan_key.side_effect = track_create

        # Perform concurrent moves
        with ThreadPoolExecutor(max_workers=3) as executor:
            futures = [
                executor.submit(admin.move_key_to_plan, "key_id_concurrent", "METERED"),
                executor.submit(admin.move_key_to_plan, "key_id_concurrent", "FREE_10K"),
                executor.submit(admin.move_key_to_plan, "key_id_concurrent", "METERED"),
            ]

            for future in futures:
                with contextlib.suppress(Exception):
                    future.result()  # Some may fail due to conflicts

        # Verify operations were attempted
        assert len(operations) > 0
        assert any(op[0] == "delete" for op in operations)
        assert any(op[0] == "create" for op in operations)

    def test_usage_calculation_edge_cases(self, adapters):
        """Test edge cases in usage calculation."""
        admin, usage, mock_client = adapters

        # Test 1: No usage data
        mock_client.get_usage_plan_key.return_value = {
            "id": "key_id_no_usage",
            "value": "key_no_usage",
        }
        mock_client.get_usage.return_value = {"usagePlanId": "METERED", "values": {}}

        now = datetime.now(UTC)
        hour_ago = now - timedelta(hours=1)

        usage_count = usage.get_usage("METERED", "key_no_usage", hour_ago, now)
        assert usage_count == 0

        # Test 2: Usage data with zeros
        mock_client.get_usage.return_value = {
            "usagePlanId": "METERED",
            "values": {"Production": [{"2025-08-20": 0}]},
        }

        usage_count = usage.get_usage("METERED", "key_zero", hour_ago, now)
        assert usage_count == 0

        # Test 3: Very large usage numbers
        mock_client.get_usage.return_value = {
            "usagePlanId": "METERED",
            "values": {"Production": [{"2025-08-20": 9999999}]},
        }

        usage_count = usage.get_usage("METERED", "key_large", hour_ago, now)
        assert usage_count == 9999999

    def test_plan_discovery(self, adapters):
        """Test discovering which plan a key belongs to."""
        admin, usage, mock_client = adapters

        mock_paginator = Mock()
        mock_client.get_paginator.return_value = mock_paginator

        # Test finding key in different plans
        test_cases = [
            ("key_in_free", "FREE_10K", True),
            ("key_in_metered", "METERED", True),
            ("key_not_found", "FREE_10K", False),
        ]

        for api_key, _expected_plan, should_find in test_cases:
            if should_find:
                mock_paginator.paginate.return_value = [
                    {"items": [{"id": f"id_{api_key}", "value": api_key}]}
                ]
            else:
                mock_paginator.paginate.return_value = [{"items": []}]

            # Attempt to find and move
            if should_find:
                admin.move_key_to_plan(f"id_{api_key}", "METERED")
                mock_client.delete_usage_plan_key.assert_called()
            else:
                # Key not found - operation should handle gracefully
                admin.move_key_to_plan(f"id_{api_key}", "METERED")
                # May or may not call delete depending on implementation
