import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { useRouter } from 'next/navigation';
import { CheckoutClient } from '../checkout-client';

vi.mock('next/navigation', () => ({
  useRouter: vi.fn(),
}));

vi.mock('@stripe/stripe-js', () => ({
  loadStripe: vi.fn(() => Promise.resolve(null)),
}));

vi.mock('@stripe/react-stripe-js', () => ({
  Elements: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
  PaymentElement: () => <div data-testid="stripe-payment-element" />,
  useStripe: () => null,
  useElements: () => null,
}));

const cartGetMock = vi.fn();
const initiateMock = vi.fn();
const cancelMock = vi.fn();
vi.mock('@/lib/api', () => ({
  api: {
    cart: { get: (...a: unknown[]) => cartGetMock(...a) },
    checkout: {
      initiate: (...a: unknown[]) => initiateMock(...a),
      cancel: (...a: unknown[]) => cancelMock(...a),
    },
  },
}));

function wrap(node: React.ReactNode) {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  return <QueryClientProvider client={qc}>{node}</QueryClientProvider>;
}

const cartResponse = {
  data: {
    item_count: 1,
    subtotal: 2500,
    shipping_total: 800,
    total: 3300,
    stores: [
      {
        store: { id: 's1', name: 'Revive', logo_image: null },
        subtotal: 2500,
        shipping: 800,
        free_shipping_threshold: null,
        items: [
          {
            id: 'ci1',
            added_at: '2026-05-01T00:00:00Z',
            item: {
              id: 'item-1',
              title: 'Vintage Tee',
              price: 2500,
              image_url: null,
              condition: 'EUC',
              is_available: true,
            },
          },
        ],
      },
    ],
  },
};

const validAddress = {
  first_name: 'Jane',
  last_name: 'Doe',
  street: '1 Main St',
  city: 'Portland',
  state: 'OR',
  zip: '97201',
};

function fillAddress() {
  // Form order: first_name, last_name, street, city, state, zip
  const inputs = screen.getAllByRole('textbox');
  const values = [
    validAddress.first_name,
    validAddress.last_name,
    validAddress.street,
    validAddress.city,
    validAddress.state,
    validAddress.zip,
  ];
  inputs.forEach((input, i) => {
    fireEvent.change(input, { target: { value: values[i] } });
  });
}

describe('CheckoutClient', () => {
  const push = vi.fn();

  beforeEach(() => {
    push.mockClear();
    cartGetMock.mockReset();
    initiateMock.mockReset();
    cancelMock.mockReset();
    vi.mocked(useRouter).mockReturnValue({
      push,
      back: vi.fn(),
      forward: vi.fn(),
      refresh: vi.fn(),
      replace: vi.fn(),
      prefetch: vi.fn(),
    } as unknown as ReturnType<typeof useRouter>);
  });

  it('redirects logic shows empty-cart UI when cart is empty', async () => {
    cartGetMock.mockResolvedValue({
      data: { item_count: 0, subtotal: 0, shipping_total: 0, total: 0, stores: [] },
    });
    render(wrap(<CheckoutClient />));
    await waitFor(() =>
      expect(screen.getByText('Your cart is empty')).toBeInTheDocument(),
    );
  });

  it('continue button is disabled until all address fields are filled', async () => {
    cartGetMock.mockResolvedValue(cartResponse);
    render(wrap(<CheckoutClient />));
    await waitFor(() =>
      expect(
        screen.getByRole('button', { name: /Continue to Payment/ }),
      ).toBeDisabled(),
    );
    fillAddress();
    expect(
      screen.getByRole('button', { name: /Continue to Payment/ }),
    ).not.toBeDisabled();
  });

  it('successful initiate transitions to payment step with Stripe element', async () => {
    cartGetMock.mockResolvedValue(cartResponse);
    initiateMock.mockResolvedValue({
      data: {
        checkout_id: 'co_1',
        client_secret: 'pi_secret',
        subtotal: 2500,
        discount_total: 0,
        shipping_total: 800,
        tax_total: 0,
        total: 3300,
        stores: [],
      },
    });
    render(wrap(<CheckoutClient />));
    await waitFor(() =>
      expect(screen.getByText('Order Details')).toBeInTheDocument(),
    );
    fillAddress();
    fireEvent.click(screen.getByRole('button', { name: /Continue to Payment/ }));
    await waitFor(() =>
      expect(screen.getByTestId('stripe-payment-element')).toBeInTheDocument(),
    );
    expect(initiateMock).toHaveBeenCalledWith(validAddress, []);
  });

  it('items_unavailable conflict shows the modal with updated totals', async () => {
    cartGetMock.mockResolvedValue(cartResponse);
    initiateMock.mockRejectedValue({
      error: 'items_unavailable',
      unavailable_items: [{ id: 'item-1', title: 'Vintage Tee', reason: 'sold' }],
      available_items: [],
      updated_totals: { subtotal: 0, shipping_total: 0, total: 0 },
    });
    render(wrap(<CheckoutClient />));
    await waitFor(() =>
      expect(screen.getByText('Order Details')).toBeInTheDocument(),
    );
    fillAddress();
    fireEvent.click(screen.getByRole('button', { name: /Continue to Payment/ }));
    await waitFor(() =>
      expect(
        screen.getByText('Some items are no longer available'),
      ).toBeInTheDocument(),
    );
    expect(screen.getByRole('button', { name: /Back to Cart/ })).toBeInTheDocument();
  });

  it('"continue with" in conflict modal re-initiates with excluded ids', async () => {
    cartGetMock.mockResolvedValue(cartResponse);
    initiateMock.mockRejectedValueOnce({
      error: 'items_unavailable',
      unavailable_items: [{ id: 'item-1', title: 'Vintage Tee', reason: 'sold' }],
      available_items: [{ id: 'item-2', title: 'Other', price: 1000 }],
      updated_totals: { subtotal: 1000, shipping_total: 500, total: 1500 },
    });
    initiateMock.mockResolvedValueOnce({
      data: {
        checkout_id: 'co_2',
        client_secret: 'pi_secret_2',
        subtotal: 1000,
        discount_total: 0,
        shipping_total: 500,
        tax_total: 0,
        total: 1500,
        stores: [],
      },
    });
    render(wrap(<CheckoutClient />));
    await waitFor(() =>
      expect(screen.getByText('Order Details')).toBeInTheDocument(),
    );
    fillAddress();
    fireEvent.click(screen.getByRole('button', { name: /Continue to Payment/ }));
    await waitFor(() =>
      expect(
        screen.getByText('Some items are no longer available'),
      ).toBeInTheDocument(),
    );
    fireEvent.click(screen.getByRole('button', { name: /Continue with/ }));
    await waitFor(() => expect(initiateMock).toHaveBeenCalledTimes(2));
    expect(initiateMock.mock.calls[1]).toEqual([validAddress, ['item-1']]);
  });
});
