"""Integration tests for Cognito API key linking functionality."""

from unittest.mock import MagicMock

import boto3
import pytest
from moto import mock_aws

from augint_billing_lib.adapters.ddb_repo import DdbRepo
from augint_billing_lib.models import Link
from augint_billing_lib.service import BillingService


@pytest.mark.integration
class TestCognitoIntegration:
    """Integration tests for Cognito-based API key linking."""

    @mock_aws
    def test_customer_created_auto_links_keys(self):
        """Test that customer.created event automatically links API keys."""
        # Setup DynamoDB
        dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
        table = dynamodb.create_table(
            TableName="test-links",
            KeySchema=[{"AttributeName": "api_key_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "api_key_id", "AttributeType": "S"},
                {"AttributeName": "stripe_customer_id", "AttributeType": "S"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "gsi_stripe_customer",
                    "KeySchema": [{"AttributeName": "stripe_customer_id", "KeyType": "HASH"}],
                    "Projection": {"ProjectionType": "ALL"},
                }
            ],
            BillingMode="PAY_PER_REQUEST",
        )

        # Setup adapters
        repo = DdbRepo(table)
        stripe_adapter = MagicMock()
        cognito_adapter = MagicMock()

        # Mock Cognito responses
        cognito_adapter.find_user_by_email.return_value = "cognito-user-123"
        cognito_adapter.list_api_keys_for_user.return_value = [
            "key_abc123",
            "key_xyz789",
        ]

        # Create service
        service = BillingService(
            repo=repo,
            stripe=stripe_adapter,
            cognito=cognito_adapter,
        )

        # Simulate customer.created event
        event = {
            "type": "customer.created",
            "data": {
                "object": {
                    "id": "cus_test123",
                    "email": "newuser@example.com",
                    "metadata": {},
                }
            },
        }

        # Act
        result = service.handle_stripe_event(event)

        # Assert
        assert result["target_plan"] == "free"
        assert result["stripe_customer_id"] == "cus_test123"

        # Verify API keys were linked
        cognito_adapter.find_user_by_email.assert_called_once_with("newuser@example.com")
        cognito_adapter.list_api_keys_for_user.assert_called_once_with("cognito-user-123")

        # Check database
        link1 = repo.get_by_api_key("key_abc123")
        assert link1.stripe_customer_id == "cus_test123"
        assert link1.cognito_user_id == "cognito-user-123"
        assert link1.plan == "free"
        assert link1.usage_plan_id == "FREE_10K"

        link2 = repo.get_by_api_key("key_xyz789")
        assert link2.stripe_customer_id == "cus_test123"

        # Verify both keys are linked to the customer
        customer_links = repo.get_by_customer("cus_test123")
        assert len(customer_links) == 2

    @mock_aws
    def test_auto_link_with_existing_customer(self):
        """Test that auto-linking is skipped for customers with existing links."""
        # Setup DynamoDB
        dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
        table = dynamodb.create_table(
            TableName="test-links",
            KeySchema=[{"AttributeName": "api_key_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "api_key_id", "AttributeType": "S"},
                {"AttributeName": "stripe_customer_id", "AttributeType": "S"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "gsi_stripe_customer",
                    "KeySchema": [{"AttributeName": "stripe_customer_id", "KeyType": "HASH"}],
                    "Projection": {"ProjectionType": "ALL"},
                }
            ],
            BillingMode="PAY_PER_REQUEST",
        )

        repo = DdbRepo(table)

        # Pre-populate with existing link
        existing_link = Link(
            api_key_id="existing_key",
            stripe_customer_id="cus_test123",
            plan="free",
            usage_plan_id="FREE_10K",
        )
        repo.save(existing_link)

        # Setup service
        stripe_adapter = MagicMock()
        cognito_adapter = MagicMock()
        service = BillingService(
            repo=repo,
            stripe=stripe_adapter,
            cognito=cognito_adapter,
        )

        # Simulate customer.created event
        event = {
            "type": "customer.created",
            "data": {
                "object": {
                    "id": "cus_test123",
                    "email": "existing@example.com",
                    "metadata": {},
                }
            },
        }

        # Act
        result = service.handle_stripe_event(event)

        # Assert
        assert result["target_plan"] == "free"

        # Cognito should not be called for existing customer
        cognito_adapter.find_user_by_email.assert_not_called()
        cognito_adapter.list_api_keys_for_user.assert_not_called()

        # Only the original link should exist
        customer_links = repo.get_by_customer("cus_test123")
        assert len(customer_links) == 1
        assert customer_links[0].api_key_id == "existing_key"

    @mock_aws
    def test_auto_link_with_metadata_user_id(self):
        """Test that Cognito user ID from metadata is used when available."""
        # Setup DynamoDB
        dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
        table = dynamodb.create_table(
            TableName="test-links",
            KeySchema=[{"AttributeName": "api_key_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "api_key_id", "AttributeType": "S"},
                {"AttributeName": "stripe_customer_id", "AttributeType": "S"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "gsi_stripe_customer",
                    "KeySchema": [{"AttributeName": "stripe_customer_id", "KeyType": "HASH"}],
                    "Projection": {"ProjectionType": "ALL"},
                }
            ],
            BillingMode="PAY_PER_REQUEST",
        )

        repo = DdbRepo(table)
        stripe_adapter = MagicMock()
        cognito_adapter = MagicMock()

        # Mock Cognito responses
        cognito_adapter.list_api_keys_for_user.return_value = ["key_from_metadata"]

        # Create service
        service = BillingService(
            repo=repo,
            stripe=stripe_adapter,
            cognito=cognito_adapter,
        )

        # Simulate customer.created event with cognito_user_id in metadata
        event = {
            "type": "customer.created",
            "data": {
                "object": {
                    "id": "cus_test456",
                    "email": "metadata@example.com",
                    "metadata": {"cognito_user_id": "metadata-user-789"},
                }
            },
        }

        # Act
        result = service.handle_stripe_event(event)

        # Assert
        assert result["target_plan"] == "free"

        # Should not look up by email when ID is in metadata
        cognito_adapter.find_user_by_email.assert_not_called()
        cognito_adapter.list_api_keys_for_user.assert_called_once_with("metadata-user-789")

        # Check database
        link = repo.get_by_api_key("key_from_metadata")
        assert link.stripe_customer_id == "cus_test456"
        assert link.cognito_user_id == "metadata-user-789"

    @mock_aws
    def test_auto_link_handles_duplicate_keys(self):
        """Test that keys already linked to other customers are skipped."""
        # Setup DynamoDB
        dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
        table = dynamodb.create_table(
            TableName="test-links",
            KeySchema=[{"AttributeName": "api_key_id", "KeyType": "HASH"}],
            AttributeDefinitions=[
                {"AttributeName": "api_key_id", "AttributeType": "S"},
                {"AttributeName": "stripe_customer_id", "AttributeType": "S"},
            ],
            GlobalSecondaryIndexes=[
                {
                    "IndexName": "gsi_stripe_customer",
                    "KeySchema": [{"AttributeName": "stripe_customer_id", "KeyType": "HASH"}],
                    "Projection": {"ProjectionType": "ALL"},
                }
            ],
            BillingMode="PAY_PER_REQUEST",
        )

        repo = DdbRepo(table)

        # Pre-populate with a key linked to another customer
        existing_link = Link(
            api_key_id="key_already_linked",
            stripe_customer_id="cus_other",
            plan="metered",
            usage_plan_id="METERED",
        )
        repo.save(existing_link)

        stripe_adapter = MagicMock()
        cognito_adapter = MagicMock()

        # Mock Cognito to return both a linked and unlinked key
        cognito_adapter.find_user_by_email.return_value = "cognito-user-456"
        cognito_adapter.list_api_keys_for_user.return_value = [
            "key_already_linked",  # This one should be skipped
            "key_new",  # This one should be linked
        ]

        service = BillingService(
            repo=repo,
            stripe=stripe_adapter,
            cognito=cognito_adapter,
        )

        # Simulate customer.created event
        event = {
            "type": "customer.created",
            "data": {
                "object": {
                    "id": "cus_test789",
                    "email": "newuser2@example.com",
                    "metadata": {},
                }
            },
        }

        # Act
        result = service.handle_stripe_event(event)

        # Assert
        assert result["target_plan"] == "free"

        # Check that only the new key was linked
        new_link = repo.get_by_api_key("key_new")
        assert new_link.stripe_customer_id == "cus_test789"

        # The already linked key should still belong to the original customer
        existing = repo.get_by_api_key("key_already_linked")
        assert existing.stripe_customer_id == "cus_other"

        # New customer should have only one key
        customer_links = repo.get_by_customer("cus_test789")
        assert len(customer_links) == 1
        assert customer_links[0].api_key_id == "key_new"
