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, usePathname, useSearchParams } from 'next/navigation';
import { useAuthStore } from '@/stores/auth';
import type { Payout } from '@alqove/api-client';
import { SellerPayoutsClient } from '../seller-payouts-client';

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

vi.mock('@/stores/auth', () => ({
  useAuthStore: vi.fn(),
}));

const listForStoreMock = vi.fn();
vi.mock('@/lib/api', () => ({
  api: {
    payouts: {
      listForStore: (...a: unknown[]) => listForStoreMock(...a),
    },
  },
}));

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

const STORE_ID = 'store-1';

const basePayout: Payout = {
  id: 'p1',
  store_id: STORE_ID,
  state: 'succeeded' as const,
  period_start: '2026-04-01T00:00:00Z',
  period_end: '2026-04-30T00:00:00Z',
  scheduled_for: '2026-05-02T00:00:00Z',
  gross_cents: 12500,
  debits_cents: 350,
  net_cents: 12150,
  stripe_transfer_id: 'tr_test_abc123',
  transferred_at: '2026-05-02T12:00:00Z',
  failed_at: null,
  failure_reason: null,
  retries: 0,
  created_at: '2026-04-30T00:00:00Z',
  updated_at: '2026-05-02T12:00:00Z',
};

function paginatedResponse(
  rows: (typeof basePayout)[],
  overrides: Partial<{
    current_page: number;
    last_page: number;
    total: number;
  }> = {},
) {
  return {
    data: rows,
    meta: {
      current_page: overrides.current_page ?? 1,
      last_page: overrides.last_page ?? 1,
      per_page: 20,
      total: overrides.total ?? rows.length,
      from: rows.length ? 1 : null,
      to: rows.length || null,
    },
    links: { first: null, last: null, prev: null, next: null },
  };
}

describe('SellerPayoutsClient', () => {
  const replace = vi.fn();
  let currentParams = new URLSearchParams('');

  beforeEach(() => {
    listForStoreMock.mockReset();
    replace.mockReset();
    currentParams = new URLSearchParams('');
    vi.mocked(useRouter).mockReturnValue({
      replace: (url: string) => {
        replace(url);
        const qs = url.split('?')[1] ?? '';
        currentParams = new URLSearchParams(qs);
      },
      push: vi.fn(),
      back: vi.fn(),
      forward: vi.fn(),
      refresh: vi.fn(),
      prefetch: vi.fn(),
    } as unknown as ReturnType<typeof useRouter>);
    vi.mocked(useSearchParams).mockImplementation(
      () => currentParams as unknown as ReturnType<typeof useSearchParams>,
    );
    vi.mocked(usePathname).mockReturnValue('/seller/payouts');
    vi.mocked(useAuthStore).mockImplementation(((selector?: (s: unknown) => unknown) => {
      const state = {
        user: { id: 'u1', name: 'Test', store_id: 'legacy-not-selected' },
        selectedStoreId: STORE_ID,
        memberships: [{ store_id: STORE_ID }],
        isLoading: false,
      };
      return selector ? selector(state) : state;
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
    }) as any);
  });

  it('renders rows when payouts are present', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([basePayout]));
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('payouts-row-p1')).toBeInTheDocument(),
    );
    const badge = screen.getByTestId('payout-state-badge');
    expect(badge.textContent).toBe('Cleared');
  });

  it('renders empty state when there are no payouts', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerPayoutsClient />));
    expect(await screen.findByTestId('payouts-empty')).toBeInTheDocument();
  });

  it('renders loading skeleton initially', () => {
    // Never-resolving promise keeps the query in loading state.
    listForStoreMock.mockReturnValue(new Promise(() => {}));
    render(wrap(<SellerPayoutsClient />));
    expect(screen.getByTestId('payouts-loading')).toBeInTheDocument();
  });

  it('renders error state on API failure', async () => {
    listForStoreMock.mockRejectedValue(new Error('boom'));
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('payouts-error')).toBeInTheDocument(),
    );
  });

  it('default filter "all" passes no state to the API', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() => expect(listForStoreMock).toHaveBeenCalled());
    const [, params] = listForStoreMock.mock.calls[0];
    expect(params.state).toBeUndefined();
    expect(params.page).toBe(1);
  });

  it('clicking a state filter chip updates the URL', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerPayoutsClient />));
    fireEvent.click(screen.getByTestId('payouts-filter-scheduled'));
    expect(replace).toHaveBeenCalledWith('/seller/payouts?state=scheduled');
  });

  it('All chip clears the state param', async () => {
    currentParams = new URLSearchParams('state=scheduled');
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerPayoutsClient />));
    fireEvent.click(screen.getByTestId('payouts-filter-all'));
    expect(replace).toHaveBeenCalledWith('/seller/payouts');
  });

  it('shows pagination controls when last_page > 1 and advances page', async () => {
    listForStoreMock.mockResolvedValue(
      paginatedResponse([basePayout], {
        current_page: 1,
        last_page: 3,
        total: 60,
      }),
    );
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() =>
      expect(screen.getByText(/Page 1 of 3/i)).toBeInTheDocument(),
    );
    const prev = screen.getByRole('button', { name: /Previous/i });
    const next = screen.getByRole('button', { name: /Next/i });
    expect(prev).toBeDisabled();
    expect(next).toBeEnabled();
    fireEvent.click(next);
    expect(replace).toHaveBeenCalledWith('/seller/payouts?page=2');
  });

  it('hides pagination controls for a single page', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([basePayout]));
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('payouts-row-p1')).toBeInTheDocument(),
    );
    expect(
      screen.queryByRole('button', { name: /Previous/i }),
    ).not.toBeInTheDocument();
  });

  it('renders the Stripe transfer id as a test-mode link when present', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([basePayout]));
    render(wrap(<SellerPayoutsClient />));
    const link = await screen.findByTestId('payouts-transfer-link-p1');
    expect(link).toHaveAttribute(
      'href',
      'https://dashboard.stripe.com/test/connect/transfers/tr_test_abc123',
    );
    expect(link).toHaveAttribute('target', '_blank');
  });

  it('renders a dash when stripe_transfer_id is null', async () => {
    listForStoreMock.mockResolvedValue(
      paginatedResponse([{ ...basePayout, stripe_transfer_id: null }]),
    );
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('payouts-row-p1')).toBeInTheDocument(),
    );
    expect(
      screen.queryByTestId('payouts-transfer-link-p1'),
    ).not.toBeInTheDocument();
    expect(screen.getByText('—')).toBeInTheDocument();
  });

  it('formats currency cells for gross, debits, and net', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([basePayout]));
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('payouts-row-p1')).toBeInTheDocument(),
    );
    expect(screen.getByText('$125.00')).toBeInTheDocument();
    expect(screen.getByText('$3.50')).toBeInTheDocument();
    expect(screen.getByText('$121.50')).toBeInTheDocument();
  });

  it('passes the storeId from the auth store to the endpoint', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerPayoutsClient />));
    await waitFor(() => expect(listForStoreMock).toHaveBeenCalled());
    expect(listForStoreMock.mock.calls[0][0]).toBe(STORE_ID);
  });
});
