/**
 * Tests for GiftCardPaymentPanel Component
 *
 * Features:
 * - Gift card code input (manual entry or scan)
 * - Look up button to check balance
 * - Shows card balance when found
 * - Amount to redeem input
 * - Apply button to add gift card payment
 * - Error handling for invalid/expired cards
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import GiftCardPaymentPanel from '../GiftCardPaymentPanel';
import * as useGiftCardsHooks from '../../hooks/useGiftCards';
import type { GiftCard } from '../../types';

// Mock the useGiftCards hooks
vi.mock('../../hooks/useGiftCards');

describe('GiftCardPaymentPanel', () => {
  let queryClient: QueryClient;
  const mockOnApply = vi.fn();
  const mockOnCancel = vi.fn();

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

    // eslint-disable-next-line react/display-name
    return ({ children }: { children: React.ReactNode }) =>
      React.createElement(QueryClientProvider, { client: queryClient }, children);
  };

  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should render gift card input form', () => {
    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: false,
      isError: false,
      data: undefined,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByPlaceholderText(/enter gift card code/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /lookup/i })).toBeInTheDocument();
  });

  it('should allow user to enter gift card code', async () => {
    const user = userEvent.setup();

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: false,
      isError: false,
      data: undefined,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    const input = screen.getByPlaceholderText(/enter gift card code/i);
    await user.type(input, 'GC-ABC123');

    expect(input).toHaveValue('GC-ABC123');
  });

  it('should lookup gift card when lookup button is clicked', async () => {
    const user = userEvent.setup();
    const mockMutate = vi.fn();

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: mockMutate,
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: false,
      isError: false,
      data: undefined,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    const input = screen.getByPlaceholderText(/enter gift card code/i);
    const lookupButton = screen.getByRole('button', { name: /lookup/i });

    await user.type(input, 'GC-ABC123');
    await user.click(lookupButton);

    expect(mockMutate).toHaveBeenCalledWith('GC-ABC123');
  });

  it('should display gift card balance when lookup succeeds', () => {
    const mockGiftCard: GiftCard = {
      id: 1,
      code: 'GC-ABC123',
      initial_balance_cents: 10000,
      current_balance_cents: 7500,
      status: 'active',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: null,
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: mockGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/GC-ABC123/i)).toBeInTheDocument();
    expect(screen.getByText(/\$75\.00/)).toBeInTheDocument();
  });

  it('should show loading state during lookup', () => {
    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: true,
      isSuccess: false,
      isError: false,
      data: undefined,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

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

  it('should show error for invalid gift card', () => {
    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: false,
      isError: true,
      data: undefined,
      error: { message: 'Gift card not found' } as any,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/gift card not found/i)).toBeInTheDocument();
  });

  it('should show error for expired gift card', () => {
    const expiredGiftCard: GiftCard = {
      id: 1,
      code: 'GC-EXPIRED',
      initial_balance_cents: 5000,
      current_balance_cents: 5000,
      status: 'expired',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: '2024-06-01T00:00:00Z',
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: expiredGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/expired/i)).toBeInTheDocument();
  });

  it('should show error for depleted gift card', () => {
    const depletedGiftCard: GiftCard = {
      id: 1,
      code: 'GC-DEPLETED',
      initial_balance_cents: 5000,
      current_balance_cents: 0,
      status: 'depleted',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: null,
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: depletedGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/no balance remaining/i)).toBeInTheDocument();
  });

  it('should default redemption amount to remaining balance or amount due, whichever is less', () => {
    const mockGiftCard: GiftCard = {
      id: 1,
      code: 'GC-ABC123',
      initial_balance_cents: 10000,
      current_balance_cents: 7500,
      status: 'active',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: null,
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: mockGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    // FormCurrencyInput displays value in dollars as formatted text
    // With amountDue=5000 cents ($50), this should be the default
    expect(screen.getByDisplayValue('$50.00')).toBeInTheDocument();
  });

  it('should allow user to change redemption amount', async () => {
    const user = userEvent.setup();

    const mockGiftCard: GiftCard = {
      id: 1,
      code: 'GC-ABC123',
      initial_balance_cents: 10000,
      current_balance_cents: 10000,
      status: 'active',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: null,
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: mockGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    // Find the currency input by its placeholder or current value
    const amountInput = screen.getByDisplayValue('$50.00');
    await user.clear(amountInput);
    await user.type(amountInput, '25');

    // After typing "25", the display should show "$0.25" (25 cents)
    expect(screen.getByDisplayValue('$0.25')).toBeInTheDocument();
  });

  it('should call onApply with correct payment info when Apply is clicked', async () => {
    const user = userEvent.setup();

    const mockGiftCard: GiftCard = {
      id: 1,
      code: 'GC-ABC123',
      initial_balance_cents: 10000,
      current_balance_cents: 7500,
      status: 'active',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: null,
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: mockGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    const applyButton = screen.getByRole('button', { name: /apply/i });
    await user.click(applyButton);

    expect(mockOnApply).toHaveBeenCalledWith({
      gift_card_code: 'GC-ABC123',
      amount_cents: 5000,
      gift_card: mockGiftCard,
    });
  });

  it('should not allow applying more than gift card balance', async () => {
    const user = userEvent.setup();

    const mockGiftCard: GiftCard = {
      id: 1,
      code: 'GC-ABC123',
      initial_balance_cents: 5000,
      current_balance_cents: 2500,
      status: 'active',
      purchased_by: null,
      recipient_email: '',
      recipient_name: '',
      created_at: '2024-01-01T00:00:00Z',
      expires_at: null,
    };

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: true,
      isError: false,
      data: mockGiftCard,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    // Default amount should be $25 (2500 cents) since that's the card balance
    const amountInput = screen.getByDisplayValue('$25.00');
    await user.clear(amountInput);
    await user.type(amountInput, '5000'); // Try to redeem $50.00 (5000 cents) when balance is $25

    const applyButton = screen.getByRole('button', { name: /apply/i });
    await user.click(applyButton);

    // Should show error, not call onApply
    expect(screen.getByText(/exceeds gift card balance/i)).toBeInTheDocument();
    expect(mockOnApply).not.toHaveBeenCalled();
  });

  it('should call onCancel when Cancel button is clicked', async () => {
    const user = userEvent.setup();

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: vi.fn(),
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: false,
      isError: false,
      data: undefined,
      error: null,
      reset: vi.fn(),
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

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

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

  it('should reset form when a new lookup is performed', async () => {
    const user = userEvent.setup();
    const mockMutate = vi.fn();
    const mockReset = vi.fn();

    vi.mocked(useGiftCardsHooks.useLookupGiftCard).mockReturnValue({
      mutate: mockMutate,
      mutateAsync: vi.fn(),
      isPending: false,
      isSuccess: false,
      isError: false,
      data: undefined,
      error: null,
      reset: mockReset,
    } as any);

    render(
      <GiftCardPaymentPanel
        amountDueCents={5000}
        onApply={mockOnApply}
        onCancel={mockOnCancel}
      />,
      { wrapper: createWrapper() }
    );

    const input = screen.getByPlaceholderText(/enter gift card code/i);
    const lookupButton = screen.getByRole('button', { name: /lookup/i });

    await user.type(input, 'GC-ABC123');
    await user.click(lookupButton);

    // Clear and lookup another card
    await user.clear(input);
    await user.type(input, 'GC-XYZ789');

    expect(mockReset).toHaveBeenCalled();
  });
});
