import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';

// Mock the payments API module
vi.mock('../../api/payments', () => ({
  getPaymentConfig: vi.fn(),
  getApiKeys: vi.fn(),
  validateApiKeys: vi.fn(),
  saveApiKeys: vi.fn(),
  revalidateApiKeys: vi.fn(),
  deleteApiKeys: vi.fn(),
  getConnectStatus: vi.fn(),
  initiateConnectOnboarding: vi.fn(),
  refreshConnectOnboardingLink: vi.fn(),
}));

import {
  usePaymentConfig,
  useApiKeys,
  useValidateApiKeys,
  useSaveApiKeys,
  useRevalidateApiKeys,
  useDeleteApiKeys,
  useConnectStatus,
  useConnectOnboarding,
  useRefreshConnectLink,
  paymentKeys,
} from '../usePayments';
import * as paymentsApi from '../../api/payments';

// Create wrapper with fresh QueryClient for each test
const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return function Wrapper({ children }: { children: React.ReactNode }) {
    return React.createElement(QueryClientProvider, { client: queryClient }, children);
  };
};

describe('usePayments hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('paymentKeys', () => {
    it('generates correct query keys', () => {
      expect(paymentKeys.all).toEqual(['payments']);
      expect(paymentKeys.config()).toEqual(['payments', 'config']);
      expect(paymentKeys.apiKeys()).toEqual(['payments', 'apiKeys']);
      expect(paymentKeys.connectStatus()).toEqual(['payments', 'connectStatus']);
    });
  });

  describe('usePaymentConfig', () => {
    it('fetches payment configuration', async () => {
      const mockConfig = {
        payment_mode: 'direct_api' as const,
        tier: 'free',
        tier_allows_payments: true,
        stripe_configured: true,
        can_accept_payments: true,
        api_keys: {
          id: 1,
          status: 'active' as const,
          secret_key_masked: 'sk_test_****1234',
          publishable_key_masked: 'pk_test_****5678',
          last_validated_at: '2025-12-07T10:00:00Z',
          stripe_account_id: 'acct_123',
          stripe_account_name: 'Test Business',
          validation_error: '',
          created_at: '2025-12-01T10:00:00Z',
          updated_at: '2025-12-07T10:00:00Z',
        },
        connect_account: null,
      };

      vi.mocked(paymentsApi.getPaymentConfig).mockResolvedValue({ data: mockConfig } as any);

      const { result } = renderHook(() => usePaymentConfig(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(paymentsApi.getPaymentConfig).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockConfig);
    });

    it('uses 30 second staleTime', async () => {
      vi.mocked(paymentsApi.getPaymentConfig).mockResolvedValue({ data: {} } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      renderHook(() => usePaymentConfig(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(paymentKeys.config());
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(paymentKeys.config());
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useApiKeys', () => {
    it('fetches current API keys configuration', async () => {
      const mockApiKeys = {
        configured: true,
        id: 1,
        status: 'active' as const,
        secret_key_masked: 'sk_test_****1234',
        publishable_key_masked: 'pk_test_****5678',
        last_validated_at: '2025-12-07T10:00:00Z',
        stripe_account_id: 'acct_123',
        stripe_account_name: 'Test Business',
        validation_error: '',
      };

      vi.mocked(paymentsApi.getApiKeys).mockResolvedValue({ data: mockApiKeys } as any);

      const { result } = renderHook(() => useApiKeys(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(paymentsApi.getApiKeys).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockApiKeys);
    });

    it('handles unconfigured state', async () => {
      const mockApiKeys = {
        configured: false,
        message: 'No API keys configured',
      };

      vi.mocked(paymentsApi.getApiKeys).mockResolvedValue({ data: mockApiKeys } as any);

      const { result } = renderHook(() => useApiKeys(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data?.configured).toBe(false);
      expect(result.current.data?.message).toBe('No API keys configured');
    });
  });

  describe('useValidateApiKeys', () => {
    it('validates API keys successfully', async () => {
      const mockValidationResult = {
        valid: true,
        account_id: 'acct_123',
        account_name: 'Test Account',
        environment: 'test',
      };

      vi.mocked(paymentsApi.validateApiKeys).mockResolvedValue({ data: mockValidationResult } as any);

      const { result } = renderHook(() => useValidateApiKeys(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync({
          secretKey: 'sk_test_123',
          publishableKey: 'pk_test_456',
        });
        expect(response).toEqual(mockValidationResult);
      });

      expect(paymentsApi.validateApiKeys).toHaveBeenCalledWith('sk_test_123', 'pk_test_456');
    });

    it('handles validation failure', async () => {
      const mockValidationResult = {
        valid: false,
        error: 'Invalid API keys',
      };

      vi.mocked(paymentsApi.validateApiKeys).mockResolvedValue({ data: mockValidationResult } as any);

      const { result } = renderHook(() => useValidateApiKeys(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync({
          secretKey: 'sk_test_invalid',
          publishableKey: 'pk_test_invalid',
        });
        expect(response.valid).toBe(false);
        expect(response.error).toBe('Invalid API keys');
      });
    });
  });

  describe('useSaveApiKeys', () => {
    it('saves API keys successfully', async () => {
      const mockSavedKeys = {
        id: 1,
        status: 'active' as const,
        secret_key_masked: 'sk_test_****1234',
        publishable_key_masked: 'pk_test_****5678',
        last_validated_at: '2025-12-07T10:00:00Z',
        stripe_account_id: 'acct_123',
        stripe_account_name: 'Test Business',
        validation_error: '',
        created_at: '2025-12-07T10:00:00Z',
        updated_at: '2025-12-07T10:00:00Z',
      };

      vi.mocked(paymentsApi.saveApiKeys).mockResolvedValue({ data: mockSavedKeys } as any);

      const { result } = renderHook(() => useSaveApiKeys(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync({
          secretKey: 'sk_test_123',
          publishableKey: 'pk_test_456',
        });
        expect(response).toEqual(mockSavedKeys);
      });

      expect(paymentsApi.saveApiKeys).toHaveBeenCalledWith('sk_test_123', 'pk_test_456');
    });

    it('invalidates payment config and api keys queries on success', async () => {
      vi.mocked(paymentsApi.saveApiKeys).mockResolvedValue({ data: {} } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries');

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      const { result } = renderHook(() => useSaveApiKeys(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          secretKey: 'sk_test_123',
          publishableKey: 'pk_test_456',
        });
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.config() });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.apiKeys() });
    });
  });

  describe('useRevalidateApiKeys', () => {
    it('revalidates stored API keys', async () => {
      const mockValidationResult = {
        valid: true,
        account_id: 'acct_123',
        account_name: 'Test Account',
        environment: 'test',
      };

      vi.mocked(paymentsApi.revalidateApiKeys).mockResolvedValue({ data: mockValidationResult } as any);

      const { result } = renderHook(() => useRevalidateApiKeys(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync();
        expect(response).toEqual(mockValidationResult);
      });

      expect(paymentsApi.revalidateApiKeys).toHaveBeenCalledTimes(1);
    });

    it('invalidates payment config and api keys queries on success', async () => {
      vi.mocked(paymentsApi.revalidateApiKeys).mockResolvedValue({ data: { valid: true } } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries');

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      const { result } = renderHook(() => useRevalidateApiKeys(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync();
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.config() });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.apiKeys() });
    });
  });

  describe('useDeleteApiKeys', () => {
    it('deletes API keys successfully', async () => {
      const mockDeleteResponse = {
        success: true,
        message: 'API keys deleted successfully',
      };

      vi.mocked(paymentsApi.deleteApiKeys).mockResolvedValue({ data: mockDeleteResponse } as any);

      const { result } = renderHook(() => useDeleteApiKeys(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync();
        expect(response).toEqual(mockDeleteResponse);
      });

      expect(paymentsApi.deleteApiKeys).toHaveBeenCalledTimes(1);
    });

    it('invalidates payment config and api keys queries on success', async () => {
      vi.mocked(paymentsApi.deleteApiKeys).mockResolvedValue({ data: { success: true } } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries');

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      const { result } = renderHook(() => useDeleteApiKeys(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync();
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.config() });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.apiKeys() });
    });
  });

  describe('useConnectStatus', () => {
    it('fetches Connect account status', async () => {
      const mockConnectStatus = {
        id: 1,
        business: 1,
        business_name: 'Test Business',
        business_subdomain: 'test',
        stripe_account_id: 'acct_connect_123',
        account_type: 'standard' as const,
        status: 'active' as const,
        charges_enabled: true,
        payouts_enabled: true,
        details_submitted: true,
        onboarding_complete: true,
        onboarding_link: null,
        onboarding_link_expires_at: null,
        is_onboarding_link_valid: false,
        created_at: '2025-12-01T10:00:00Z',
        updated_at: '2025-12-07T10:00:00Z',
      };

      vi.mocked(paymentsApi.getConnectStatus).mockResolvedValue({ data: mockConnectStatus } as any);

      const { result } = renderHook(() => useConnectStatus(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(paymentsApi.getConnectStatus).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockConnectStatus);
    });

    it('handles onboarding state with valid link', async () => {
      const mockConnectStatus = {
        id: 1,
        business: 1,
        business_name: 'Test Business',
        business_subdomain: 'test',
        stripe_account_id: 'acct_connect_123',
        account_type: 'custom' as const,
        status: 'onboarding' as const,
        charges_enabled: false,
        payouts_enabled: false,
        details_submitted: false,
        onboarding_complete: false,
        onboarding_link: 'https://connect.stripe.com/setup/...',
        onboarding_link_expires_at: '2025-12-08T10:00:00Z',
        is_onboarding_link_valid: true,
        created_at: '2025-12-07T10:00:00Z',
        updated_at: '2025-12-07T10:00:00Z',
      };

      vi.mocked(paymentsApi.getConnectStatus).mockResolvedValue({ data: mockConnectStatus } as any);

      const { result } = renderHook(() => useConnectStatus(), {
        wrapper: createWrapper(),
      });

      await waitFor(() => {
        expect(result.current.isSuccess).toBe(true);
      });

      expect(result.current.data?.onboarding_link).toBe('https://connect.stripe.com/setup/...');
      expect(result.current.data?.is_onboarding_link_valid).toBe(true);
    });

    it('is enabled by default', async () => {
      vi.mocked(paymentsApi.getConnectStatus).mockResolvedValue({ data: {} } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      renderHook(() => useConnectStatus(), { wrapper });

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

  describe('useConnectOnboarding', () => {
    it('initiates Connect onboarding successfully', async () => {
      const mockOnboardingResponse = {
        account_type: 'standard' as const,
        url: 'https://connect.stripe.com/setup/s/acct_123/abc123',
        stripe_account_id: 'acct_123',
      };

      vi.mocked(paymentsApi.initiateConnectOnboarding).mockResolvedValue({ data: mockOnboardingResponse } as any);

      const { result } = renderHook(() => useConnectOnboarding(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync({
          refreshUrl: 'http://test.lvh.me:5173/payments/refresh',
          returnUrl: 'http://test.lvh.me:5173/payments/complete',
        });
        expect(response).toEqual(mockOnboardingResponse);
      });

      expect(paymentsApi.initiateConnectOnboarding).toHaveBeenCalledWith(
        'http://test.lvh.me:5173/payments/refresh',
        'http://test.lvh.me:5173/payments/complete'
      );
    });

    it('invalidates payment config and connect status queries on success', async () => {
      vi.mocked(paymentsApi.initiateConnectOnboarding).mockResolvedValue({
        data: { account_type: 'standard', url: 'https://stripe.com' }
      } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries');

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      const { result } = renderHook(() => useConnectOnboarding(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          refreshUrl: 'http://test.lvh.me:5173/refresh',
          returnUrl: 'http://test.lvh.me:5173/return',
        });
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.config() });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.connectStatus() });
    });
  });

  describe('useRefreshConnectLink', () => {
    it('refreshes Connect onboarding link successfully', async () => {
      const mockRefreshResponse = {
        url: 'https://connect.stripe.com/setup/s/acct_123/xyz789',
      };

      vi.mocked(paymentsApi.refreshConnectOnboardingLink).mockResolvedValue({ data: mockRefreshResponse } as any);

      const { result } = renderHook(() => useRefreshConnectLink(), {
        wrapper: createWrapper(),
      });

      await act(async () => {
        const response = await result.current.mutateAsync({
          refreshUrl: 'http://test.lvh.me:5173/payments/refresh',
          returnUrl: 'http://test.lvh.me:5173/payments/complete',
        });
        expect(response).toEqual(mockRefreshResponse);
      });

      expect(paymentsApi.refreshConnectOnboardingLink).toHaveBeenCalledWith(
        'http://test.lvh.me:5173/payments/refresh',
        'http://test.lvh.me:5173/payments/complete'
      );
    });

    it('invalidates connect status query on success', async () => {
      vi.mocked(paymentsApi.refreshConnectOnboardingLink).mockResolvedValue({
        data: { url: 'https://stripe.com' }
      } as any);

      const queryClient = new QueryClient({
        defaultOptions: {
          queries: { retry: false },
          mutations: { retry: false },
        },
      });

      const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries');

      const wrapper = ({ children }: { children: React.ReactNode }) =>
        React.createElement(QueryClientProvider, { client: queryClient }, children);

      const { result } = renderHook(() => useRefreshConnectLink(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          refreshUrl: 'http://test.lvh.me:5173/refresh',
          returnUrl: 'http://test.lvh.me:5173/return',
        });
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: paymentKeys.connectStatus() });
      // Should NOT invalidate config on refresh (only on initial onboarding)
      expect(invalidateQueriesSpy).not.toHaveBeenCalledWith({ queryKey: paymentKeys.config() });
    });
  });
});
