/**
 * E2E Tests: Stripe Elements Integration (Frontend)
 *
 * Epic: OQI-005 - Payments & Stripe
 * Blocker: BLOCKER-002 (ST4.2)
 *
 * Tests validate:
 * - Stripe Elements renders correctly (iframe)
 * - Card data NEVER stored in React state
 * - confirmCardPayment called with clientSecret
 * - NO card data sent to backend
 * - Payment confirmation happens via Stripe
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Elements, CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import '@testing-library/jest-dom';

// Mock Stripe
vi.mock('@stripe/stripe-js');
vi.mock('@stripe/react-stripe-js', async () => {
  const actual = await vi.importActual('@stripe/react-stripe-js');
  return {
    ...actual,
    useStripe: vi.fn(),
    useElements: vi.fn(),
    CardElement: vi.fn(() => <div data-testid="stripe-card-element">Stripe Card Element</div>),
  };
});

// Mock API client
vi.mock('../../lib/apiClient', () => ({
  apiClient: {
    post: vi.fn(),
  },
}));

import { apiClient } from '../../lib/apiClient';
import DepositForm from '../../modules/investment/components/DepositForm';

describe('E2E: Stripe Elements Integration (Frontend)', () => {
  let mockStripe: any;
  let mockElements: any;
  let mockCardElement: any;

  beforeEach(() => {
    // Setup Stripe mocks
    mockCardElement = {
      mount: vi.fn(),
      unmount: vi.fn(),
      on: vi.fn(),
      update: vi.fn(),
    };

    mockElements = {
      getElement: vi.fn(() => mockCardElement),
    };

    mockStripe = {
      confirmCardPayment: vi.fn(),
      elements: vi.fn(() => mockElements),
    };

    (useStripe as vi.Mock).mockReturnValue(mockStripe);
    (useElements as vi.Mock).mockReturnValue(mockElements);
    (loadStripe as vi.Mock).mockResolvedValue(mockStripe);

    // Clear all mocks
    vi.clearAllMocks();
  });

  // ==========================================================================
  // TEST 1: Stripe CardElement Renders (Iframe)
  // ==========================================================================

  describe('Stripe CardElement Rendering', () => {
    it('should render Stripe CardElement (NOT native input)', () => {
      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      // Verify Stripe CardElement is rendered
      const cardElement = screen.getByTestId('stripe-card-element');
      expect(cardElement).toBeInTheDocument();

      // CRITICAL: Verify NO native card inputs
      expect(screen.queryByPlaceholderText(/card number/i)).not.toBeInTheDocument();
      expect(screen.queryByPlaceholderText(/cvv/i)).not.toBeInTheDocument();
      expect(screen.queryByPlaceholderText(/expiry/i)).not.toBeInTheDocument();
    });

    it('should NOT store card data in React state', () => {
      const { container } = render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      // Search for any state variables that might store card data
      const componentHTML = container.innerHTML;

      // ❌ Prohibited: Card data in DOM
      expect(componentHTML).not.toContain('cardNumber');
      expect(componentHTML).not.toContain('cvv');
      expect(componentHTML).not.toContain('expiryDate');
      expect(componentHTML).not.toContain('4242424242424242'); // Example card number
    });
  });

  // ==========================================================================
  // TEST 2: Payment Intent Flow (Backend Integration)
  // ==========================================================================

  describe('Payment Intent Flow', () => {
    it('should create Payment Intent and confirm with Stripe', async () => {
      // Mock backend response (Payment Intent)
      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            clientSecret: 'pi_test_123_secret_456',
            paymentIntentId: 'pi_test_123',
          },
        },
      });

      // Mock Stripe confirmCardPayment success
      mockStripe.confirmCardPayment.mockResolvedValue({
        paymentIntent: {
          id: 'pi_test_123',
          status: 'succeeded',
        },
        error: null,
      });

      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      // Step 1: Fill amount
      const amountInput = screen.getByLabelText(/amount/i);
      fireEvent.change(amountInput, { target: { value: '100' } });

      // Step 2: Submit form
      const submitButton = screen.getByRole('button', { name: /deposit/i });
      fireEvent.click(submitButton);

      // Wait for async operations
      await waitFor(() => {
        // Verify Payment Intent created (backend call)
        expect(apiClient.post).toHaveBeenCalledWith(
          '/api/v1/payments/wallet/deposit',
          expect.objectContaining({
            amount: 100,
            currency: 'USD',
          })
        );

        // CRITICAL: Verify NO card data sent to backend
        const backendCall = (apiClient.post as vi.Mock).mock.calls[0][1];
        expect(backendCall).not.toHaveProperty('cardNumber');
        expect(backendCall).not.toHaveProperty('cvv');
        expect(backendCall).not.toHaveProperty('expiryDate');
      });

      // Verify Stripe confirmCardPayment called
      await waitFor(() => {
        expect(mockStripe.confirmCardPayment).toHaveBeenCalledWith(
          'pi_test_123_secret_456',
          expect.objectContaining({
            payment_method: {
              card: mockCardElement, // ← Card element (iframe)
            },
          })
        );
      });

      // Verify success message
      await waitFor(() => {
        expect(screen.getByText(/deposit successful/i)).toBeInTheDocument();
      });
    });

    it('should handle Stripe card validation errors', async () => {
      // Mock Stripe confirmCardPayment failure (invalid card)
      mockStripe.confirmCardPayment.mockResolvedValue({
        error: {
          type: 'card_error',
          code: 'invalid_number',
          message: 'Your card number is invalid.',
        },
        paymentIntent: null,
      });

      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            clientSecret: 'pi_test_456_secret_789',
          },
        },
      });

      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      // Fill and submit
      const amountInput = screen.getByLabelText(/amount/i);
      fireEvent.change(amountInput, { target: { value: '50' } });

      const submitButton = screen.getByRole('button', { name: /deposit/i });
      fireEvent.click(submitButton);

      // Verify error displayed
      await waitFor(() => {
        expect(screen.getByText(/your card number is invalid/i)).toBeInTheDocument();
      });

      // CRITICAL: Verify error came from Stripe, not our validation
      // (This proves card validation happens in Stripe iframe, not our code)
      expect(mockStripe.confirmCardPayment).toHaveBeenCalled();
    });
  });

  // ==========================================================================
  // TEST 3: Checkout Session Flow (Stripe Hosted Page)
  // ==========================================================================

  describe('Checkout Session Flow (Stripe Hosted)', () => {
    it('should redirect to Stripe hosted checkout page', async () => {
      // Mock backend response (Checkout Session)
      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            sessionId: 'cs_test_123',
            url: 'https://checkout.stripe.com/pay/cs_test_123',
          },
        },
      });

      // Mock window.location.href
      delete (window as any).location;
      (window as any).location = { href: '' };

      render(
        <Elements stripe={mockStripe}>
          <div>
            <button
              onClick={async () => {
                const response = await apiClient.post('/api/v1/payments/checkout', {
                  planId: 'plan_basic',
                  billingCycle: 'monthly',
                  successUrl: 'https://app.example.com/success',
                  cancelUrl: 'https://app.example.com/cancel',
                });
                window.location.href = response.data.data.url;
              }}
            >
              Subscribe
            </button>
          </div>
        </Elements>
      );

      const subscribeButton = screen.getByRole('button', { name: /subscribe/i });
      fireEvent.click(subscribeButton);

      await waitFor(() => {
        // Verify redirected to Stripe hosted page
        expect(window.location.href).toBe('https://checkout.stripe.com/pay/cs_test_123');

        // CRITICAL: Verify redirect is to Stripe domain (not our domain)
        expect(window.location.href).toContain('checkout.stripe.com');
      });

      // CRITICAL: Verify NO card input on our page
      // (User will enter card data on Stripe's hosted page, not ours)
      expect(screen.queryByTestId('stripe-card-element')).not.toBeInTheDocument();
    });
  });

  // ==========================================================================
  // TEST 4: Payment Method Attachment (Tokenization)
  // ==========================================================================

  describe('Payment Method Attachment', () => {
    it('should attach payment method using Stripe token', async () => {
      // Mock Stripe createPaymentMethod (tokenization)
      mockStripe.createPaymentMethod = vi.fn().mockResolvedValue({
        paymentMethod: {
          id: 'pm_test_123',
          type: 'card',
          card: {
            brand: 'visa',
            last4: '4242',
          },
        },
        error: null,
      });

      // Mock backend attach payment method
      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            paymentMethodId: 'pm_test_123',
          },
        },
      });

      render(
        <Elements stripe={mockStripe}>
          <div>
            <CardElement />
            <button
              onClick={async () => {
                // Step 1: Create PaymentMethod (tokenize card)
                const { paymentMethod } = await mockStripe.createPaymentMethod({
                  type: 'card',
                  card: mockCardElement,
                });

                // Step 2: Send token to backend (NOT card data)
                await apiClient.post('/api/v1/payments/methods', {
                  paymentMethodId: paymentMethod.id, // ✅ Token
                });
              }}
            >
              Add Card
            </button>
          </div>
        </Elements>
      );

      const addCardButton = screen.getByRole('button', { name: /add card/i });
      fireEvent.click(addCardButton);

      await waitFor(() => {
        // Verify Stripe createPaymentMethod called (tokenization)
        expect(mockStripe.createPaymentMethod).toHaveBeenCalledWith({
          type: 'card',
          card: mockCardElement,
        });

        // Verify backend received token (NOT raw card)
        expect(apiClient.post).toHaveBeenCalledWith(
          '/api/v1/payments/methods',
          expect.objectContaining({
            paymentMethodId: 'pm_test_123', // ✅ Token
          })
        );

        // CRITICAL: Verify NO raw card data sent
        const backendCall = (apiClient.post as vi.Mock).mock.calls[0][1];
        expect(backendCall).not.toHaveProperty('cardNumber');
        expect(backendCall).not.toHaveProperty('cvv');
        expect(backendCall).not.toHaveProperty('expiryDate');
      });
    });
  });

  // ==========================================================================
  // TEST 5: Component State Validation (No Sensitive Data)
  // ==========================================================================

  describe('Component State: No Sensitive Data', () => {
    it('should NOT have card data in component state', () => {
      const { container } = render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      // Get component props/state from DOM
      const componentText = container.textContent || '';

      // ❌ Prohibited: Card data in component state
      expect(componentText).not.toContain('4242424242424242'); // Card number
      expect(componentText).not.toContain('cvv');
      expect(componentText).not.toContain('cvc');

      // Search for useState/useReducer with card data
      // (This would appear in React DevTools, but we can't easily test here)
      // Manual verification required: Check React DevTools
    });
  });

  // ==========================================================================
  // TEST 6: Error Handling (Stripe Errors)
  // ==========================================================================

  describe('Error Handling', () => {
    it('should display Stripe validation errors', async () => {
      // Mock Stripe error (card declined)
      mockStripe.confirmCardPayment.mockResolvedValue({
        error: {
          type: 'card_error',
          code: 'card_declined',
          message: 'Your card was declined.',
        },
        paymentIntent: null,
      });

      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            clientSecret: 'pi_test_error',
          },
        },
      });

      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      // Submit form
      const amountInput = screen.getByLabelText(/amount/i);
      fireEvent.change(amountInput, { target: { value: '100' } });

      const submitButton = screen.getByRole('button', { name: /deposit/i });
      fireEvent.click(submitButton);

      // Verify error displayed
      await waitFor(() => {
        expect(screen.getByText(/your card was declined/i)).toBeInTheDocument();
      });
    });

    it('should handle network errors gracefully', async () => {
      // Mock network error
      (apiClient.post as vi.Mock).mockRejectedValue(new Error('Network error'));

      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      const amountInput = screen.getByLabelText(/amount/i);
      fireEvent.change(amountInput, { target: { value: '100' } });

      const submitButton = screen.getByRole('button', { name: /deposit/i });
      fireEvent.click(submitButton);

      await waitFor(() => {
        expect(screen.getByText(/network error/i)).toBeInTheDocument();
      });
    });
  });

  // ==========================================================================
  // TEST 7: Security Best Practices
  // ==========================================================================

  describe('Security Best Practices', () => {
    it('should use HTTPS for all API calls', async () => {
      // Mock successful payment
      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            clientSecret: 'pi_test_https',
          },
        },
      });

      mockStripe.confirmCardPayment.mockResolvedValue({
        paymentIntent: { id: 'pi_test_https', status: 'succeeded' },
        error: null,
      });

      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      const amountInput = screen.getByLabelText(/amount/i);
      fireEvent.change(amountInput, { target: { value: '100' } });

      const submitButton = screen.getByRole('button', { name: /deposit/i });
      fireEvent.click(submitButton);

      await waitFor(() => {
        // In production, verify API base URL is HTTPS
        // (Mock doesn't have URL, but implementation should enforce HTTPS)
        expect(apiClient.post).toHaveBeenCalled();
      });
    });

    it('should NOT log sensitive data to console', async () => {
      // Spy on console methods
      const consoleLogSpy = vi.spyOn(console, 'log');
      const consoleErrorSpy = vi.spyOn(console, 'error');

      (apiClient.post as vi.Mock).mockResolvedValue({
        data: {
          success: true,
          data: {
            clientSecret: 'pi_test_console',
          },
        },
      });

      mockStripe.confirmCardPayment.mockResolvedValue({
        paymentIntent: { id: 'pi_test_console', status: 'succeeded' },
        error: null,
      });

      render(
        <Elements stripe={mockStripe}>
          <DepositForm onSuccess={vi.fn()} />
        </Elements>
      );

      const amountInput = screen.getByLabelText(/amount/i);
      fireEvent.change(amountInput, { target: { value: '100' } });

      const submitButton = screen.getByRole('button', { name: /deposit/i });
      fireEvent.click(submitButton);

      await waitFor(() => {
        expect(mockStripe.confirmCardPayment).toHaveBeenCalled();
      });

      // Verify NO sensitive data logged
      const allLogs = [
        ...consoleLogSpy.mock.calls,
        ...consoleErrorSpy.mock.calls,
      ].flat().join(' ');

      expect(allLogs).not.toContain('4242424242424242'); // Card number
      expect(allLogs).not.toContain('pi_test_console_secret'); // Client secret
      expect(allLogs).not.toContain('cvv');

      consoleLogSpy.mockRestore();
      consoleErrorSpy.mockRestore();
    });
  });
});
