"""Unit tests for automatic API key linking functionality."""

from unittest.mock import MagicMock

from augint_billing_lib.models import Link
from augint_billing_lib.service import BillingService


class TestApiKeyLinking:
    """Test suite for automatic API key linking."""

    def test_link_api_keys_success(self):
        """Test successful linking of API keys to a Stripe customer."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        # Mock existing links check
        repo.get_by_customer.return_value = []

        # Mock Cognito user lookup
        cognito.find_user_by_email.return_value = "cognito-user-123"

        # Mock API key discovery
        cognito.list_api_keys_for_user.return_value = ["key_abc", "key_xyz"]

        # Mock repo checks for existing keys
        repo.get_by_api_key.side_effect = KeyError("Not found")

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
            metadata=None,
        )

        # Assert
        assert result == 2
        cognito.find_user_by_email.assert_called_once_with("test@example.com")
        cognito.list_api_keys_for_user.assert_called_once_with("cognito-user-123")
        assert repo.save.call_count == 2

        # Verify the links were created correctly
        saved_links = [call[0][0] for call in repo.save.call_args_list]
        assert saved_links[0].api_key_id == "key_abc"
        assert saved_links[0].stripe_customer_id == "cus_test123"
        assert saved_links[0].cognito_user_id == "cognito-user-123"
        assert saved_links[0].plan == "free"
        assert saved_links[0].usage_plan_id == "FREE_10K"

    def test_link_api_keys_no_cognito_port(self):
        """Test that linking returns 0 when Cognito port is not configured."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=None)

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
        )

        # Assert
        assert result == 0
        repo.get_by_customer.assert_not_called()

    def test_link_api_keys_customer_already_has_links(self):
        """Test that linking is skipped if customer already has links."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        # Mock existing links
        existing_link = Link(
            api_key_id="existing_key",
            stripe_customer_id="cus_test123",
            plan="free",
        )
        repo.get_by_customer.return_value = [existing_link]

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
        )

        # Assert
        assert result == 0
        cognito.find_user_by_email.assert_not_called()

    def test_link_api_keys_no_cognito_user_found(self):
        """Test that linking returns 0 when no Cognito user is found."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        repo.get_by_customer.return_value = []
        cognito.find_user_by_email.return_value = None

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
        )

        # Assert
        assert result == 0
        cognito.list_api_keys_for_user.assert_not_called()

    def test_link_api_keys_no_keys_found(self):
        """Test that linking returns 0 when user has no API keys."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        repo.get_by_customer.return_value = []
        cognito.find_user_by_email.return_value = "cognito-user-123"
        cognito.list_api_keys_for_user.return_value = []

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
        )

        # Assert
        assert result == 0
        repo.save.assert_not_called()

    def test_link_api_keys_skip_already_linked_keys(self):
        """Test that keys already linked to another customer are skipped."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        repo.get_by_customer.return_value = []
        cognito.find_user_by_email.return_value = "cognito-user-123"
        cognito.list_api_keys_for_user.return_value = ["key_abc", "key_xyz"]

        # First key is already linked, second is not
        existing_link = Link(
            api_key_id="key_abc",
            stripe_customer_id="cus_other",
            plan="free",
        )

        def get_by_api_key_side_effect(key_id):
            if key_id == "key_abc":
                return existing_link
            raise KeyError("Not found")

        repo.get_by_api_key.side_effect = get_by_api_key_side_effect

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
        )

        # Assert
        assert result == 1  # Only one key linked
        assert repo.save.call_count == 1
        saved_link = repo.save.call_args[0][0]
        assert saved_link.api_key_id == "key_xyz"

    def test_link_api_keys_uses_metadata_cognito_id(self):
        """Test that Cognito user ID from metadata is used if available."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        repo.get_by_customer.return_value = []
        cognito.list_api_keys_for_user.return_value = ["key_abc"]
        repo.get_by_api_key.side_effect = KeyError("Not found")

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
            metadata={"cognito_user_id": "metadata-user-456"},
        )

        # Assert
        assert result == 1
        # Should not look up by email when ID is in metadata
        cognito.find_user_by_email.assert_not_called()
        cognito.list_api_keys_for_user.assert_called_once_with("metadata-user-456")

    def test_link_api_keys_handles_exceptions_gracefully(self):
        """Test that exceptions during linking don't crash the process."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        repo.get_by_customer.return_value = []
        cognito.find_user_by_email.side_effect = Exception("Cognito error")

        # Act
        result = service.link_api_keys_to_customer(
            stripe_customer_id="cus_test123",
            customer_email="test@example.com",
        )

        # Assert
        assert result == 0  # Returns 0 on error
        repo.save.assert_not_called()

    def test_customer_created_event_triggers_linking(self):
        """Test that customer.created event triggers auto-linking."""
        # Arrange
        repo = MagicMock()
        stripe_port = MagicMock()
        cognito = MagicMock()
        service = BillingService(repo=repo, stripe=stripe_port, cognito=cognito)

        # Mock auto-linking
        repo.get_by_customer.return_value = []
        cognito.find_user_by_email.return_value = "cognito-user-123"
        cognito.list_api_keys_for_user.return_value = ["key_abc"]
        repo.get_by_api_key.side_effect = KeyError("Not found")

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

        # Act
        result = service.handle_stripe_event(event)

        # Assert
        assert result["target_plan"] == "free"
        assert result["stripe_customer_id"] == "cus_test123"
        cognito.find_user_by_email.assert_called_once_with("test@example.com")
        assert repo.save.call_count == 1
