/**
 * StripeConnectOnboarding Component Tests
 *
 * Tests for the Stripe Connect onboarding flow that helps
 * users set up their earnings accounts.
 */

import React from 'react';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { vi, type MockedFunction } from 'vitest';
import StripeConnectOnboarding from '../../components/StripeConnectOnboarding';
import { stripeAPI } from '../../services/api';
import { loadConnectAndInitialize } from '@stripe/connect-js';

// Mock dependencies
vi.mock('../../services/api', () => ({
  stripeAPI: {
    createConnectedAccount: vi.fn(),
    createAccountSession: vi.fn(),
  },
}));

vi.mock('@stripe/connect-js', () => ({
  loadConnectAndInitialize: vi.fn(),
}));

vi.mock('@stripe/react-connect-js', () => ({
  ConnectComponentsProvider: ({ children }: { children: React.ReactNode }) => (
    <div data-testid="connect-provider">{children}</div>
  ),
  ConnectAccountOnboarding: ({ onExit }: { onExit?: () => void }) => (
    <div data-testid="connect-onboarding">
      <button onClick={onExit}>Complete Onboarding</button>
    </div>
  ),
}));

const mockedCreateConnectedAccount = stripeAPI.createConnectedAccount as MockedFunction<typeof stripeAPI.createConnectedAccount>;
const mockedCreateAccountSession = stripeAPI.createAccountSession as MockedFunction<typeof stripeAPI.createAccountSession>;
const mockedLoadConnectAndInitialize = loadConnectAndInitialize as MockedFunction<typeof loadConnectAndInitialize>;

describe('StripeConnectOnboarding', () => {
  const mockOnComplete = vi.fn();
  const mockOnCancel = vi.fn();

  const mockStripeConnectInstance = {
    create: vi.fn(),
  };

  beforeEach(() => {
    vi.clearAllMocks();
    vi.stubEnv('VITE_STRIPE_PUBLISHABLE_KEY', 'pk_test_123');
    mockedCreateConnectedAccount.mockResolvedValue({ data: { accountId: 'acct_test123' } });
    mockedCreateAccountSession.mockResolvedValue({ data: { clientSecret: 'seti_test_secret' } });
    mockedLoadConnectAndInitialize.mockReturnValue(mockStripeConnectInstance as any);
  });

  afterEach(() => {
    vi.unstubAllEnvs();
  });

  describe('Initial Start State', () => {
    it('renders initial "Start" state', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      expect(screen.getByText('Start Receiving Earnings')).toBeInTheDocument();
      expect(screen.getByText('Quick Setup')).toBeInTheDocument();
      expect(screen.getByText('Secure')).toBeInTheDocument();
      expect(screen.getByText('Fast Deposits')).toBeInTheDocument();
    });

    it('shows what to expect info', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      expect(screen.getByText(/what to expect/i)).toBeInTheDocument();
      expect(screen.getByText(/verify your identity/i)).toBeInTheDocument();
      expect(screen.getByText(/bank account details/i)).toBeInTheDocument();
    });

    it('shows Set Up Earnings button', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      expect(screen.getByRole('button', { name: /set up earnings/i })).toBeInTheDocument();
    });

    it('shows cancel button', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
    });
  });

  describe('Account Creation Flow', () => {
    it('creates account on "Set Up Earnings" click', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

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

    it('shows loading during account creation', async () => {
      mockedCreateConnectedAccount.mockImplementation(() => new Promise(() => {}));

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText('Creating your earnings account...')).toBeInTheDocument();
      });

      expect(screen.getByRole('status')).toBeInTheDocument();
    });

    it('initializes Stripe Connect after account creation', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(mockedLoadConnectAndInitialize).toHaveBeenCalledWith(
          expect.objectContaining({
            publishableKey: expect.any(String),
            fetchClientSecret: expect.any(Function),
          })
        );
      });
    });
  });

  describe('Onboarding State', () => {
    it('renders embedded onboarding form', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByTestId('connect-onboarding')).toBeInTheDocument();
      });
    });

    it('shows "Complete Your Earnings Setup" title in onboarding', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText('Complete Your Earnings Setup')).toBeInTheDocument();
      });
    });

    it('shows security message in onboarding', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText(/securely processed by stripe/i)).toBeInTheDocument();
      });
    });
  });

  describe('Onboarding Completion', () => {
    it('handles onboarding completion callback', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByTestId('connect-onboarding')).toBeInTheDocument();
      });

      // Click the mocked complete button
      const completeButton = screen.getByRole('button', { name: /complete onboarding/i });
      fireEvent.click(completeButton);

      expect(mockOnComplete).toHaveBeenCalled();
    });
  });

  describe('Cancel Handling', () => {
    it('handles cancel button in start state', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

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

      expect(mockOnCancel).toHaveBeenCalled();
    });

    it('handles close button via modal header', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const closeButton = screen.getByRole('button', { name: '' }); // btn-close has no text
      if (closeButton.classList.contains('btn-close')) {
        fireEvent.click(closeButton);
        expect(mockOnCancel).toHaveBeenCalled();
      }
    });
  });

  describe('Error Handling', () => {
    it('handles missing publishable key error', async () => {
      // Create a custom mock that throws on initialization
      mockedLoadConnectAndInitialize.mockImplementation(() => {
        throw new Error('Stripe publishable key not configured');
      });

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText(/stripe publishable key not configured/i)).toBeInTheDocument();
      });
    });

    it('handles account creation failure', async () => {
      mockedCreateConnectedAccount.mockRejectedValue({
        response: { data: { error: 'Account creation failed' } },
      });

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText('Account creation failed')).toBeInTheDocument();
      });
    });

    it('handles generic error message', async () => {
      mockedCreateConnectedAccount.mockRejectedValue(new Error('Network error'));

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText('Network error')).toBeInTheDocument();
      });
    });

    it('returns to start state on error', async () => {
      mockedCreateConnectedAccount.mockRejectedValue(new Error('Failed'));

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText('Start Receiving Earnings')).toBeInTheDocument();
      });
    });

    it('displays error alert with icon', async () => {
      mockedCreateConnectedAccount.mockRejectedValue({
        response: { data: { error: 'Test error' } },
      });

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByText('Test error')).toBeInTheDocument();
      });

      // Check that the error alert has the danger class
      const errorAlert = screen.getByText('Test error').closest('.alert');
      expect(errorAlert).toHaveClass('alert-danger');
    });
  });

  describe('Existing Account Handling', () => {
    it('starts in onboarding state for existing account', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
          hasExistingAccount={true}
        />
      );

      // With existing account, should show onboarding title
      expect(screen.getByText('Complete Your Earnings Setup')).toBeInTheDocument();
    });

    it('initializes onboarding for existing account', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
          hasExistingAccount={true}
        />
      );

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

    it('does not create new account for existing account', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
          hasExistingAccount={true}
        />
      );

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

      // Should not call createConnectedAccount
      expect(mockedCreateConnectedAccount).not.toHaveBeenCalled();
    });
  });

  describe('Modal Sizing', () => {
    it('uses larger modal for onboarding state', async () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        expect(screen.getByTestId('connect-onboarding')).toBeInTheDocument();
      });

      // Modal should have modal-xl class
      const modalDialog = document.querySelector('.modal-dialog');
      expect(modalDialog).toHaveClass('modal-xl');
    });

    it('uses standard modal for start state', () => {
      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const modalDialog = document.querySelector('.modal-dialog');
      expect(modalDialog).toHaveClass('modal-lg');
    });
  });

  describe('Loading States', () => {
    it('shows loading spinner during onboarding initialization', async () => {
      // Return null to simulate instance not being ready yet
      mockedLoadConnectAndInitialize.mockReturnValue(null as any);

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
          hasExistingAccount={true}
        />
      );

      await waitFor(() => {
        expect(screen.getByText('Loading onboarding form...')).toBeInTheDocument();
      });
    });

    it('disables close button while loading', async () => {
      mockedCreateConnectedAccount.mockImplementation(() => new Promise(() => {}));

      render(
        <StripeConnectOnboarding
          onComplete={mockOnComplete}
          onCancel={mockOnCancel}
        />
      );

      const setupButton = screen.getByRole('button', { name: /set up earnings/i });
      fireEvent.click(setupButton);

      await waitFor(() => {
        const closeButton = document.querySelector('.btn-close');
        expect(closeButton).toBeDisabled();
      });
    });
  });
});
