"""
Unit tests for StripeService.

Tests the Stripe Connect integration logic with mocks to avoid API calls.
"""
from decimal import Decimal
from unittest.mock import Mock, patch, MagicMock
import pytest

from smoothschedule.commerce.payments.services import StripeService, get_stripe_service_for_tenant


class TestStripeServiceInit:
    """Test StripeService initialization."""

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_init_sets_api_key(self, mock_stripe):
        """Test that initialization sets the Stripe API key."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        # Act
        with patch('smoothschedule.commerce.payments.services.settings') as mock_settings:
            mock_settings.STRIPE_SECRET_KEY = 'sk_test_xxx'
            service = StripeService(mock_tenant)

        # Assert
        assert service.tenant == mock_tenant
        assert mock_stripe.api_key == 'sk_test_xxx'


class TestStripeServiceFactory:
    """Test the factory function."""

    def test_factory_raises_without_connect_id(self):
        """Test that factory raises error if tenant has no Stripe Connect ID."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.name = 'Test Business'
        mock_tenant.stripe_connect_id = None

        # Act & Assert
        with pytest.raises(ValueError) as exc_info:
            get_stripe_service_for_tenant(mock_tenant)

        assert "does not have a Stripe Connect account" in str(exc_info.value)

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_factory_returns_service_with_connect_id(self, mock_stripe):
        """Test that factory returns StripeService when tenant has Connect ID."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        # Act
        with patch('smoothschedule.commerce.payments.services.settings'):
            service = get_stripe_service_for_tenant(mock_tenant)

        # Assert
        assert isinstance(service, StripeService)
        assert service.tenant == mock_tenant


class TestCreatePaymentIntent:
    """Test payment intent creation."""

    @patch('smoothschedule.commerce.payments.services.TransactionLink')
    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_create_payment_intent_uses_stripe_account(
        self, mock_stripe, mock_transaction_link
    ):
        """Test that payment intent uses stripe_account header."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'
        mock_tenant.id = 1
        mock_tenant.name = 'Test Business'
        mock_tenant.currency = 'USD'

        mock_event = Mock()
        mock_event.id = 100
        mock_event.title = 'Test Appointment'

        mock_pi = Mock()
        mock_pi.id = 'pi_test123'
        mock_pi.currency = 'usd'
        mock_stripe.PaymentIntent.create.return_value = mock_pi

        mock_tx = Mock()
        mock_transaction_link.objects.create.return_value = mock_tx
        mock_transaction_link.Status.PENDING = 'PENDING'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        amount = Decimal('100.00')
        pi, tx = service.create_payment_intent(mock_event, amount)

        # Assert
        mock_stripe.PaymentIntent.create.assert_called_once()
        call_kwargs = mock_stripe.PaymentIntent.create.call_args.kwargs

        # CRITICAL: Verify stripe_account header is set
        assert call_kwargs['stripe_account'] == 'acct_test123'

        # Verify amount in cents
        assert call_kwargs['amount'] == 10000  # $100 = 10000 cents

        # Verify application fee is calculated (5% default)
        assert call_kwargs['application_fee_amount'] == 500  # 5% of 10000

    @patch('smoothschedule.commerce.payments.services.TransactionLink')
    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_create_payment_intent_custom_fee(
        self, mock_stripe, mock_transaction_link
    ):
        """Test payment intent with custom application fee."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'
        mock_tenant.id = 1
        mock_tenant.name = 'Test Business'
        mock_tenant.currency = 'USD'

        mock_event = Mock()
        mock_event.id = 100
        mock_event.title = 'Test Appointment'

        mock_pi = Mock()
        mock_pi.id = 'pi_test123'
        mock_pi.currency = 'usd'
        mock_stripe.PaymentIntent.create.return_value = mock_pi

        mock_tx = Mock()
        mock_transaction_link.objects.create.return_value = mock_tx
        mock_transaction_link.Status.PENDING = 'PENDING'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act - 10% fee instead of default 5%
        amount = Decimal('100.00')
        pi, tx = service.create_payment_intent(
            mock_event, amount, application_fee_percent=Decimal('10.0')
        )

        # Assert
        call_kwargs = mock_stripe.PaymentIntent.create.call_args.kwargs
        assert call_kwargs['application_fee_amount'] == 1000  # 10% of 10000

    @patch('smoothschedule.commerce.payments.services.TransactionLink')
    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_create_payment_intent_includes_metadata(
        self, mock_stripe, mock_transaction_link
    ):
        """Test that payment intent includes proper metadata."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'
        mock_tenant.id = 42
        mock_tenant.name = 'My Business'
        mock_tenant.currency = 'EUR'

        mock_event = Mock()
        mock_event.id = 99
        mock_event.title = 'Premium Service'

        mock_pi = Mock()
        mock_pi.id = 'pi_test123'
        mock_pi.currency = 'eur'
        mock_stripe.PaymentIntent.create.return_value = mock_pi

        mock_tx = Mock()
        mock_transaction_link.objects.create.return_value = mock_tx
        mock_transaction_link.Status.PENDING = 'PENDING'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        pi, tx = service.create_payment_intent(
            mock_event, Decimal('50.00'), locale='es'
        )

        # Assert
        call_kwargs = mock_stripe.PaymentIntent.create.call_args.kwargs
        metadata = call_kwargs['metadata']

        assert metadata['event_id'] == 99
        assert metadata['event_title'] == 'Premium Service'
        assert metadata['tenant_id'] == 42
        assert metadata['tenant_name'] == 'My Business'
        assert metadata['locale'] == 'es'


class TestRefundPayment:
    """Test refund functionality."""

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_refund_uses_stripe_account(self, mock_stripe):
        """Test that refund uses stripe_account header."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        service.refund_payment('pi_test123')

        # Assert
        mock_stripe.Refund.create.assert_called_once()
        call_kwargs = mock_stripe.Refund.create.call_args.kwargs
        assert call_kwargs['stripe_account'] == 'acct_test123'
        assert call_kwargs['payment_intent'] == 'pi_test123'

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_partial_refund_converts_amount(self, mock_stripe):
        """Test that partial refund amount is converted to cents."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act - $25 partial refund
        service.refund_payment('pi_test123', amount=Decimal('25.00'))

        # Assert
        call_kwargs = mock_stripe.Refund.create.call_args.kwargs
        assert call_kwargs['amount'] == 2500  # Converted to cents


class TestPaymentMethods:
    """Test payment method operations."""

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_list_payment_methods_uses_stripe_account(self, mock_stripe):
        """Test that listing payment methods uses stripe_account header."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        service.list_payment_methods('cus_test123')

        # Assert
        mock_stripe.PaymentMethod.list.assert_called_once()
        call_kwargs = mock_stripe.PaymentMethod.list.call_args.kwargs
        assert call_kwargs['stripe_account'] == 'acct_test123'
        assert call_kwargs['customer'] == 'cus_test123'

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_detach_payment_method_uses_stripe_account(self, mock_stripe):
        """Test that detaching payment method uses stripe_account header."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        service.detach_payment_method('pm_test123')

        # Assert
        mock_stripe.PaymentMethod.detach.assert_called_once_with(
            'pm_test123',
            stripe_account='acct_test123'
        )


class TestCustomerOperations:
    """Test customer creation and retrieval."""

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_create_customer_on_connected_account(self, mock_stripe):
        """Test that new customers are created on connected account."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'
        mock_tenant.id = 1

        mock_user = Mock()
        mock_user.email = 'test@example.com'
        mock_user.full_name = 'John Doe'
        mock_user.username = 'johndoe'
        mock_user.id = 42
        mock_user.stripe_customer_id = None

        mock_customer = Mock()
        mock_customer.id = 'cus_new123'
        mock_stripe.Customer.create.return_value = mock_customer

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        customer_id = service.create_or_get_customer(mock_user)

        # Assert
        assert customer_id == 'cus_new123'
        mock_stripe.Customer.create.assert_called_once()
        call_kwargs = mock_stripe.Customer.create.call_args.kwargs
        assert call_kwargs['stripe_account'] == 'acct_test123'
        assert call_kwargs['email'] == 'test@example.com'

        # Verify user was updated
        mock_user.save.assert_called_once()

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_get_existing_customer(self, mock_stripe):
        """Test that existing customer ID is returned without creating new."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        mock_user = Mock()
        mock_user.stripe_customer_id = 'cus_existing123'

        mock_customer = Mock()
        mock_stripe.Customer.retrieve.return_value = mock_customer

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        customer_id = service.create_or_get_customer(mock_user)

        # Assert
        assert customer_id == 'cus_existing123'
        mock_stripe.Customer.create.assert_not_called()


class TestTerminalOperations:
    """Test Stripe Terminal operations."""

    @patch('smoothschedule.commerce.payments.services.stripe')
    def test_get_terminal_token_uses_stripe_account(self, mock_stripe):
        """Test that terminal token uses stripe_account header."""
        # Arrange
        mock_tenant = Mock()
        mock_tenant.stripe_connect_id = 'acct_test123'

        with patch('smoothschedule.commerce.payments.services.settings'):
            service = StripeService(mock_tenant)

        # Act
        service.get_terminal_token()

        # Assert
        mock_stripe.terminal.ConnectionToken.create.assert_called_once_with(
            stripe_account='acct_test123'
        )
