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', () => ({
  getTransactions: vi.fn(),
  getTransaction: vi.fn(),
  getTransactionSummary: vi.fn(),
  getStripeCharges: vi.fn(),
  getStripePayouts: vi.fn(),
  getStripeBalance: vi.fn(),
  exportTransactions: vi.fn(),
  getTransactionDetail: vi.fn(),
  refundTransaction: vi.fn(),
}));

import {
  useTransactions,
  useTransaction,
  useTransactionSummary,
  useStripeCharges,
  useStripePayouts,
  useStripeBalance,
  useExportTransactions,
  useInvalidateTransactions,
  useTransactionDetail,
  useRefundTransaction,
} from '../useTransactionAnalytics';
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('useTransactionAnalytics hooks', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('useTransactions', () => {
    it('fetches paginated transaction list without filters', async () => {
      const mockTransactions = {
        results: [
          {
            id: 1,
            business: 1,
            business_name: 'Test Business',
            stripe_payment_intent_id: 'pi_123',
            stripe_charge_id: 'ch_123',
            transaction_type: 'payment' as const,
            status: 'succeeded' as const,
            amount: 5000,
            amount_display: '$50.00',
            application_fee_amount: 150,
            fee_display: '$1.50',
            net_amount: 4850,
            currency: 'usd',
            customer_email: 'customer@example.com',
            customer_name: 'John Doe',
            created_at: '2025-12-07T10:00:00Z',
            updated_at: '2025-12-07T10:00:00Z',
          },
        ],
        count: 1,
        page: 1,
        page_size: 20,
        total_pages: 1,
      };

      vi.mocked(paymentsApi.getTransactions).mockResolvedValue({ data: mockTransactions } as any);

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

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

      expect(paymentsApi.getTransactions).toHaveBeenCalledWith(undefined);
      expect(result.current.data).toEqual(mockTransactions);
    });

    it('fetches transactions with filters', async () => {
      const mockTransactions = {
        results: [],
        count: 0,
        page: 1,
        page_size: 10,
        total_pages: 0,
      };

      const filters = {
        start_date: '2025-12-01',
        end_date: '2025-12-07',
        status: 'succeeded' as const,
        transaction_type: 'payment' as const,
        page: 1,
        page_size: 10,
      };

      vi.mocked(paymentsApi.getTransactions).mockResolvedValue({ data: mockTransactions } as any);

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

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

      expect(paymentsApi.getTransactions).toHaveBeenCalledWith(filters);
      expect(result.current.data).toEqual(mockTransactions);
    });

    it('uses 30 second staleTime', async () => {
      vi.mocked(paymentsApi.getTransactions).mockResolvedValue({ data: { results: [] } } 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(() => useTransactions(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['transactions', undefined]);
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(['transactions', undefined]);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });

    it('creates different query keys for different filters', async () => {
      vi.mocked(paymentsApi.getTransactions).mockResolvedValue({ data: { results: [] } } 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);

      const filters1 = { start_date: '2025-12-01' };
      const filters2 = { start_date: '2025-12-07' };

      const { rerender } = renderHook(
        ({ filters }) => useTransactions(filters),
        {
          wrapper,
          initialProps: { filters: filters1 },
        }
      );

      await waitFor(() => {
        expect(queryClient.getQueryState(['transactions', filters1])).toBeDefined();
      });

      rerender({ filters: filters2 });

      await waitFor(() => {
        expect(queryClient.getQueryState(['transactions', filters2])).toBeDefined();
      });

      expect(paymentsApi.getTransactions).toHaveBeenCalledWith(filters1);
      expect(paymentsApi.getTransactions).toHaveBeenCalledWith(filters2);
    });
  });

  describe('useTransaction', () => {
    it('fetches a single transaction by ID', async () => {
      const mockTransaction = {
        id: 1,
        business: 1,
        business_name: 'Test Business',
        stripe_payment_intent_id: 'pi_123',
        stripe_charge_id: 'ch_123',
        transaction_type: 'payment' as const,
        status: 'succeeded' as const,
        amount: 5000,
        amount_display: '$50.00',
        application_fee_amount: 150,
        fee_display: '$1.50',
        net_amount: 4850,
        currency: 'usd',
        customer_email: 'customer@example.com',
        customer_name: 'John Doe',
        created_at: '2025-12-07T10:00:00Z',
        updated_at: '2025-12-07T10:00:00Z',
      };

      vi.mocked(paymentsApi.getTransaction).mockResolvedValue({ data: mockTransaction } as any);

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

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

      expect(paymentsApi.getTransaction).toHaveBeenCalledWith(1);
      expect(result.current.data).toEqual(mockTransaction);
    });

    it('is disabled when ID is falsy', async () => {
      vi.mocked(paymentsApi.getTransaction).mockResolvedValue({ data: {} } as any);

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

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

      expect(paymentsApi.getTransaction).not.toHaveBeenCalled();
    });
  });

  describe('useTransactionSummary', () => {
    it('fetches transaction summary without filters', async () => {
      const mockSummary = {
        total_transactions: 100,
        total_volume: 50000,
        total_volume_display: '$500.00',
        total_fees: 1500,
        total_fees_display: '$15.00',
        net_revenue: 48500,
        net_revenue_display: '$485.00',
        successful_transactions: 95,
        failed_transactions: 3,
        refunded_transactions: 2,
        average_transaction: 500,
        average_transaction_display: '$5.00',
      };

      vi.mocked(paymentsApi.getTransactionSummary).mockResolvedValue({ data: mockSummary } as any);

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

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

      expect(paymentsApi.getTransactionSummary).toHaveBeenCalledWith(undefined);
      expect(result.current.data).toEqual(mockSummary);
    });

    it('fetches transaction summary with date filters', async () => {
      const mockSummary = {
        total_transactions: 50,
        total_volume: 25000,
        total_volume_display: '$250.00',
        total_fees: 750,
        total_fees_display: '$7.50',
        net_revenue: 24250,
        net_revenue_display: '$242.50',
        successful_transactions: 48,
        failed_transactions: 1,
        refunded_transactions: 1,
        average_transaction: 500,
        average_transaction_display: '$5.00',
      };

      const filters = {
        start_date: '2025-12-01',
        end_date: '2025-12-07',
      };

      vi.mocked(paymentsApi.getTransactionSummary).mockResolvedValue({ data: mockSummary } as any);

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

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

      expect(paymentsApi.getTransactionSummary).toHaveBeenCalledWith(filters);
      expect(result.current.data).toEqual(mockSummary);
    });

    it('uses 1 minute staleTime', async () => {
      vi.mocked(paymentsApi.getTransactionSummary).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(() => useTransactionSummary(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['transactionSummary', undefined]);
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(['transactionSummary', undefined]);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useStripeCharges', () => {
    it('fetches Stripe charges with default limit', async () => {
      const mockCharges = {
        charges: [
          {
            id: 'ch_123',
            amount: 5000,
            amount_display: '$50.00',
            amount_refunded: 0,
            currency: 'usd',
            status: 'succeeded',
            paid: true,
            refunded: false,
            description: 'Test charge',
            receipt_email: 'customer@example.com',
            receipt_url: 'https://stripe.com/receipt',
            created: 1733572800,
            payment_method_details: null,
            billing_details: null,
          },
        ],
        has_more: false,
      };

      vi.mocked(paymentsApi.getStripeCharges).mockResolvedValue({ data: mockCharges } as any);

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

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

      expect(paymentsApi.getStripeCharges).toHaveBeenCalledWith(20);
      expect(result.current.data).toEqual(mockCharges);
    });

    it('fetches Stripe charges with custom limit', async () => {
      const mockCharges = {
        charges: [],
        has_more: false,
      };

      vi.mocked(paymentsApi.getStripeCharges).mockResolvedValue({ data: mockCharges } as any);

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

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

      expect(paymentsApi.getStripeCharges).toHaveBeenCalledWith(50);
    });

    it('uses 30 second staleTime', async () => {
      vi.mocked(paymentsApi.getStripeCharges).mockResolvedValue({ data: { charges: [] } } 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(() => useStripeCharges(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['stripeCharges', 20]);
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(['stripeCharges', 20]);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useStripePayouts', () => {
    it('fetches Stripe payouts with default limit', async () => {
      const mockPayouts = {
        payouts: [
          {
            id: 'po_123',
            amount: 10000,
            amount_display: '$100.00',
            currency: 'usd',
            status: 'paid',
            arrival_date: 1733659200,
            created: 1733572800,
            description: 'STRIPE PAYOUT',
            destination: 'ba_123',
            failure_message: null,
            method: 'standard',
            type: 'bank_account',
          },
        ],
        has_more: false,
      };

      vi.mocked(paymentsApi.getStripePayouts).mockResolvedValue({ data: mockPayouts } as any);

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

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

      expect(paymentsApi.getStripePayouts).toHaveBeenCalledWith(20);
      expect(result.current.data).toEqual(mockPayouts);
    });

    it('fetches Stripe payouts with custom limit', async () => {
      const mockPayouts = {
        payouts: [],
        has_more: true,
      };

      vi.mocked(paymentsApi.getStripePayouts).mockResolvedValue({ data: mockPayouts } as any);

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

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

      expect(paymentsApi.getStripePayouts).toHaveBeenCalledWith(10);
    });

    it('uses 30 second staleTime', async () => {
      vi.mocked(paymentsApi.getStripePayouts).mockResolvedValue({ data: { payouts: [] } } 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(() => useStripePayouts(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['stripePayouts', 20]);
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(['stripePayouts', 20]);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useStripeBalance', () => {
    it('fetches Stripe balance', async () => {
      const mockBalance = {
        available: [
          {
            amount: 10000,
            currency: 'usd',
            amount_display: '$100.00',
          },
        ],
        pending: [
          {
            amount: 5000,
            currency: 'usd',
            amount_display: '$50.00',
          },
        ],
        available_total: 10000,
        pending_total: 5000,
      };

      vi.mocked(paymentsApi.getStripeBalance).mockResolvedValue({ data: mockBalance } as any);

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

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

      expect(paymentsApi.getStripeBalance).toHaveBeenCalledTimes(1);
      expect(result.current.data).toEqual(mockBalance);
    });

    it('uses 1 minute staleTime and 5 minute refetchInterval', async () => {
      vi.mocked(paymentsApi.getStripeBalance).mockResolvedValue({
        data: {
          available: [],
          pending: [],
          available_total: 0,
          pending_total: 0,
        },
      } 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(() => useStripeBalance(), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['stripeBalance']);
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(['stripeBalance']);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useExportTransactions', () => {
    beforeEach(() => {
      // Mock URL and Blob APIs
      global.URL.createObjectURL = vi.fn(() => 'blob:mock-url');
      global.URL.revokeObjectURL = vi.fn();

      // Spy on document methods
      vi.spyOn(document, 'createElement');
      vi.spyOn(document.body, 'appendChild');
      vi.spyOn(document.body, 'removeChild');
    });

    it('exports transactions as CSV', async () => {
      const mockBlob = { type: 'text/csv', size: 100 };
      const mockResponse = {
        data: mockBlob,
        headers: { 'content-type': 'text/csv' },
      };

      vi.mocked(paymentsApi.exportTransactions).mockResolvedValue(mockResponse as any);

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

      await act(async () => {
        await result.current.mutateAsync({
          format: 'csv',
          start_date: '2025-12-01',
          end_date: '2025-12-07',
        });
      });

      expect(paymentsApi.exportTransactions).toHaveBeenCalledWith({
        format: 'csv',
        start_date: '2025-12-01',
        end_date: '2025-12-07',
      });
    });

    it('exports transactions as XLSX', async () => {
      const mockBlob = {
        type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        size: 100,
      };
      const mockResponse = {
        data: mockBlob,
        headers: {
          'content-type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
        },
      };

      vi.mocked(paymentsApi.exportTransactions).mockResolvedValue(mockResponse as any);

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

      await act(async () => {
        await result.current.mutateAsync({
          format: 'xlsx',
        });
      });

      expect(paymentsApi.exportTransactions).toHaveBeenCalledWith({
        format: 'xlsx',
      });
    });

    it('exports transactions as PDF', async () => {
      const mockBlob = { type: 'application/pdf', size: 100 };
      const mockResponse = {
        data: mockBlob,
        headers: { 'content-type': 'application/pdf' },
      };

      vi.mocked(paymentsApi.exportTransactions).mockResolvedValue(mockResponse as any);

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

      await act(async () => {
        await result.current.mutateAsync({
          format: 'pdf',
          include_details: true,
        });
      });

      expect(paymentsApi.exportTransactions).toHaveBeenCalledWith({
        format: 'pdf',
        include_details: true,
      });
    });

    it('exports transactions as QuickBooks IIF', async () => {
      const mockBlob = { type: 'text/plain', size: 100 };
      const mockResponse = {
        data: mockBlob,
        headers: { 'content-type': 'text/plain' },
      };

      vi.mocked(paymentsApi.exportTransactions).mockResolvedValue(mockResponse as any);

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

      await act(async () => {
        await result.current.mutateAsync({
          format: 'quickbooks',
        });
      });

      expect(paymentsApi.exportTransactions).toHaveBeenCalledWith({
        format: 'quickbooks',
      });
    });

    it('triggers file download on success', async () => {
      const mockBlob = { type: 'text/csv', size: 100 };
      const mockResponse = {
        data: mockBlob,
        headers: { 'content-type': 'text/csv' },
      };

      vi.mocked(paymentsApi.exportTransactions).mockResolvedValue(mockResponse as any);

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

      await act(async () => {
        await result.current.mutateAsync({ format: 'csv' });
      });

      expect(global.URL.createObjectURL).toHaveBeenCalled();
      expect(document.createElement).toHaveBeenCalledWith('a');
      expect(document.body.appendChild).toHaveBeenCalled();
      expect(document.body.removeChild).toHaveBeenCalled();
      expect(global.URL.revokeObjectURL).toHaveBeenCalledWith('blob:mock-url');
    });

    it('uses correct file extension for each format', async () => {
      const mockBlob = { type: 'text/plain', size: 100 };
      const mockResponse = {
        data: mockBlob,
        headers: { 'content-type': 'text/plain' },
      };

      vi.mocked(paymentsApi.exportTransactions).mockResolvedValue(mockResponse as any);

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

      // CSV
      await act(async () => {
        await result.current.mutateAsync({ format: 'csv' });
      });
      const csvLink = (document.createElement as any).mock.results.find((r: any) => r.value?.download?.endsWith('.csv'))?.value;
      expect(csvLink?.download).toBe('transactions.csv');

      // XLSX
      await act(async () => {
        await result.current.mutateAsync({ format: 'xlsx' });
      });
      const xlsxLink = (document.createElement as any).mock.results.find((r: any) => r.value?.download?.endsWith('.xlsx'))?.value;
      expect(xlsxLink?.download).toBe('transactions.xlsx');

      // PDF
      await act(async () => {
        await result.current.mutateAsync({ format: 'pdf' });
      });
      const pdfLink = (document.createElement as any).mock.results.find((r: any) => r.value?.download?.endsWith('.pdf'))?.value;
      expect(pdfLink?.download).toBe('transactions.pdf');

      // QuickBooks
      await act(async () => {
        await result.current.mutateAsync({ format: 'quickbooks' });
      });
      const iifLink = (document.createElement as any).mock.results.find((r: any) => r.value?.download?.endsWith('.iif'))?.value;
      expect(iifLink?.download).toBe('transactions.iif');
    });
  });

  describe('useInvalidateTransactions', () => {
    it('invalidates all transaction-related queries', async () => {
      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(() => useInvalidateTransactions(), { wrapper });

      act(() => {
        result.current();
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['transactions'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['transactionSummary'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['stripeCharges'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['stripePayouts'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['stripeBalance'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['transactionDetail'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledTimes(6);
    });
  });

  describe('useTransactionDetail', () => {
    it('fetches detailed transaction information', async () => {
      const mockDetailedTransaction = {
        id: 1,
        business: 1,
        business_name: 'Test Business',
        stripe_payment_intent_id: 'pi_123',
        stripe_charge_id: 'ch_123',
        transaction_type: 'payment' as const,
        status: 'partially_refunded' as const,
        amount: 5000,
        amount_display: '$50.00',
        application_fee_amount: 150,
        fee_display: '$1.50',
        net_amount: 4850,
        currency: 'usd',
        customer_email: 'customer@example.com',
        customer_name: 'John Doe',
        created_at: '2025-12-07T10:00:00Z',
        updated_at: '2025-12-07T10:00:00Z',
        refunds: [
          {
            id: 're_123',
            amount: 1000,
            amount_display: '$10.00',
            status: 'succeeded',
            reason: 'requested_by_customer',
            created: 1733572800,
          },
        ],
        refundable_amount: 4000,
        total_refunded: 1000,
        can_refund: true,
        payment_method_info: {
          type: 'card',
          brand: 'visa',
          last4: '4242',
          exp_month: 12,
          exp_year: 2026,
          funding: 'credit',
        },
        description: 'Payment for service',
      };

      vi.mocked(paymentsApi.getTransactionDetail).mockResolvedValue({
        data: mockDetailedTransaction,
      } as any);

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

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

      expect(paymentsApi.getTransactionDetail).toHaveBeenCalledWith(1);
      expect(result.current.data).toEqual(mockDetailedTransaction);
    });

    it('returns null when ID is null', async () => {
      vi.mocked(paymentsApi.getTransactionDetail).mockResolvedValue({ data: null } as any);

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

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

      expect(paymentsApi.getTransactionDetail).not.toHaveBeenCalled();
    });

    it('is disabled when ID is null', async () => {
      vi.mocked(paymentsApi.getTransactionDetail).mockResolvedValue({ data: {} } as any);

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

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

      expect(paymentsApi.getTransactionDetail).not.toHaveBeenCalled();
    });

    it('uses 10 second staleTime for live data', async () => {
      vi.mocked(paymentsApi.getTransactionDetail).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(() => useTransactionDetail(1), { wrapper });

      await waitFor(() => {
        const queryState = queryClient.getQueryState(['transactionDetail', 1]);
        expect(queryState).toBeDefined();
      });

      const queryState = queryClient.getQueryState(['transactionDetail', 1]);
      expect(queryState?.dataUpdatedAt).toBeDefined();
    });
  });

  describe('useRefundTransaction', () => {
    it('issues a full refund successfully', async () => {
      const mockRefundResponse = {
        success: true,
        refund_id: 're_123',
        amount: 5000,
        amount_display: '$50.00',
        status: 'succeeded',
        reason: null,
        transaction_status: 'refunded',
      };

      vi.mocked(paymentsApi.refundTransaction).mockResolvedValue({
        data: mockRefundResponse,
      } as any);

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

      await act(async () => {
        const response = await result.current.mutateAsync({
          transactionId: 1,
        });
        expect(response).toEqual(mockRefundResponse);
      });

      expect(paymentsApi.refundTransaction).toHaveBeenCalledWith(1, undefined);
    });

    it('issues a partial refund successfully', async () => {
      const mockRefundResponse = {
        success: true,
        refund_id: 're_456',
        amount: 1000,
        amount_display: '$10.00',
        status: 'succeeded',
        reason: 'requested_by_customer',
        transaction_status: 'partially_refunded',
      };

      vi.mocked(paymentsApi.refundTransaction).mockResolvedValue({
        data: mockRefundResponse,
      } as any);

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

      const refundRequest = {
        amount: 1000,
        reason: 'requested_by_customer' as const,
      };

      await act(async () => {
        const response = await result.current.mutateAsync({
          transactionId: 1,
          request: refundRequest,
        });
        expect(response).toEqual(mockRefundResponse);
      });

      expect(paymentsApi.refundTransaction).toHaveBeenCalledWith(1, refundRequest);
    });

    it('issues refund with metadata', async () => {
      const mockRefundResponse = {
        success: true,
        refund_id: 're_789',
        amount: 2000,
        amount_display: '$20.00',
        status: 'succeeded',
        reason: 'duplicate',
        transaction_status: 'partially_refunded',
      };

      vi.mocked(paymentsApi.refundTransaction).mockResolvedValue({
        data: mockRefundResponse,
      } as any);

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

      const refundRequest = {
        amount: 2000,
        reason: 'duplicate' as const,
        metadata: {
          refund_reason: 'Duplicate charge',
          refunded_by: 'admin@example.com',
        },
      };

      await act(async () => {
        await result.current.mutateAsync({
          transactionId: 1,
          request: refundRequest,
        });
      });

      expect(paymentsApi.refundTransaction).toHaveBeenCalledWith(1, refundRequest);
    });

    it('invalidates relevant queries on success', async () => {
      vi.mocked(paymentsApi.refundTransaction).mockResolvedValue({
        data: { success: true, refund_id: 're_123' },
      } 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(() => useRefundTransaction(), { wrapper });

      await act(async () => {
        await result.current.mutateAsync({
          transactionId: 1,
        });
      });

      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['transactions'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['transactionSummary'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['transactionDetail', 1] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['stripeCharges'] });
      expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['stripeBalance'] });
    });

    it('handles refund errors', async () => {
      const mockError = new Error('Refund failed: Insufficient funds');

      vi.mocked(paymentsApi.refundTransaction).mockRejectedValue(mockError);

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

      await act(async () => {
        try {
          await result.current.mutateAsync({
            transactionId: 1,
          });
          // If no error thrown, fail the test
          throw new Error('Expected mutation to fail');
        } catch (error) {
          expect(error).toEqual(mockError);
        }
      });

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