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 { SellerStatementsClient } from '../seller-statements-client';
import type { LedgerEntry } from '@alqove/api-client';

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

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

const listForStoreMock = vi.fn();
const downloadCsvMock = vi.fn();
vi.mock('@/lib/api', () => ({
  api: {
    statements: {
      listForStore: (...a: unknown[]) => listForStoreMock(...a),
      downloadCsv: (...a: unknown[]) => downloadCsvMock(...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 baseEntry: LedgerEntry = {
  id: 'le1',
  entry_type: 'order_earned',
  direction: 'credit',
  amount_cents: 5000,
  description: 'Order #ABC',
  source_type: 'order',
  source_id: 'order-1',
  available_at: '2026-05-17T00:00:00Z',
  payout_id: null,
  created_at: '2026-05-03T10:00:00Z',
};

function paginatedResponse(
  rows: LedgerEntry[],
  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('SellerStatementsClient', () => {
  const replace = vi.fn();
  let currentParams = new URLSearchParams('');

  beforeEach(() => {
    listForStoreMock.mockReset();
    downloadCsvMock.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/statements');
    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;
    }) as unknown as typeof useAuthStore);
  });

  it('renders ledger rows with signed amounts', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([baseEntry]));
    render(wrap(<SellerStatementsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('statements-row-le1')).toBeInTheDocument(),
    );
    expect(screen.getByTestId('statements-amount-le1').textContent).toMatch(
      /\+\$50\.00/,
    );
  });

  it('shows a − sign for debit entries', async () => {
    listForStoreMock.mockResolvedValue(
      paginatedResponse([
        {
          ...baseEntry,
          id: 'le2',
          direction: 'debit',
          entry_type: 'adjustment_debit',
          amount_cents: 250,
        },
      ]),
    );
    render(wrap(<SellerStatementsClient />));
    await waitFor(() =>
      expect(screen.getByTestId('statements-amount-le2').textContent).toMatch(
        /−\$2\.50/,
      ),
    );
  });

  it('renders empty state when ledger has no entries', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerStatementsClient />));
    expect(
      await screen.findByTestId('statements-empty'),
    ).toBeInTheDocument();
  });

  it('renders loading state', () => {
    listForStoreMock.mockReturnValue(new Promise(() => {}));
    render(wrap(<SellerStatementsClient />));
    expect(screen.getByTestId('statements-loading')).toBeInTheDocument();
  });

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

  it('links payout_id to /seller/payouts when present', async () => {
    listForStoreMock.mockResolvedValue(
      paginatedResponse([{ ...baseEntry, payout_id: 'payout-xyz-123' }]),
    );
    render(wrap(<SellerStatementsClient />));
    const link = await screen.findByTestId('statements-payout-link-le1');
    expect(link).toHaveAttribute(
      'href',
      '/seller/payouts?highlight=payout-xyz-123',
    );
  });

  it('apply-range button writes from/to to URL', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerStatementsClient />));
    fireEvent.change(screen.getByTestId('statements-from'), {
      target: { value: '2026-05-01' },
    });
    fireEvent.change(screen.getByTestId('statements-to'), {
      target: { value: '2026-05-14' },
    });
    fireEvent.click(screen.getByTestId('statements-apply-range'));
    expect(replace).toHaveBeenCalledWith(
      '/seller/statements?from=2026-05-01&to=2026-05-14',
    );
  });

  it('clicking Download CSV calls the blob endpoint and triggers an a-tag click', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([baseEntry]));
    const fakeBlob = new Blob(['csv'], { type: 'text/csv' });
    downloadCsvMock.mockResolvedValue(fakeBlob);
    const createUrl = vi.fn(() => 'blob:fake');
    const revokeUrl = vi.fn();
    Object.defineProperty(window.URL, 'createObjectURL', {
      value: createUrl,
      configurable: true,
    });
    Object.defineProperty(window.URL, 'revokeObjectURL', {
      value: revokeUrl,
      configurable: true,
    });
    const clickSpy = vi
      .spyOn(HTMLAnchorElement.prototype, 'click')
      .mockImplementation(() => {});

    render(wrap(<SellerStatementsClient />));
    fireEvent.click(screen.getByTestId('statements-download-csv'));

    await waitFor(() =>
      expect(downloadCsvMock).toHaveBeenCalledWith(STORE_ID, {}),
    );
    await waitFor(() => expect(createUrl).toHaveBeenCalledWith(fakeBlob));
    expect(clickSpy).toHaveBeenCalled();
    expect(revokeUrl).toHaveBeenCalledWith('blob:fake');
    clickSpy.mockRestore();
  });

  it('Download CSV surfaces an error when the blob fetch fails', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    downloadCsvMock.mockRejectedValue(new Error('network down'));
    render(wrap(<SellerStatementsClient />));
    fireEvent.click(screen.getByTestId('statements-download-csv'));
    await waitFor(() =>
      expect(
        screen.getByTestId('statements-download-error'),
      ).toBeInTheDocument(),
    );
  });

  it('shows pagination when last_page > 1 and advances pages', async () => {
    listForStoreMock.mockResolvedValue(
      paginatedResponse([baseEntry], {
        current_page: 1,
        last_page: 3,
        total: 50,
      }),
    );
    render(wrap(<SellerStatementsClient />));
    await waitFor(() =>
      expect(screen.getByText(/Page 1 of 3/i)).toBeInTheDocument(),
    );
    fireEvent.click(screen.getByRole('button', { name: /Next/i }));
    expect(replace).toHaveBeenCalledWith('/seller/statements?page=2');
  });

  it('passes from/to from URL into the listForStore call', async () => {
    currentParams = new URLSearchParams('from=2026-05-01&to=2026-05-14');
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerStatementsClient />));
    await waitFor(() => expect(listForStoreMock).toHaveBeenCalled());
    const [, params] = listForStoreMock.mock.calls[0];
    expect(params.from).toBe('2026-05-01');
    expect(params.to).toBe('2026-05-14');
  });

  it('CSV download invocation forwards the from/to range', async () => {
    currentParams = new URLSearchParams('from=2026-05-01&to=2026-05-14');
    listForStoreMock.mockResolvedValue(paginatedResponse([baseEntry]));
    const fakeBlob = new Blob(['csv'], { type: 'text/csv' });
    downloadCsvMock.mockResolvedValue(fakeBlob);
    Object.defineProperty(window.URL, 'createObjectURL', {
      value: vi.fn(() => 'blob:fake'),
      configurable: true,
    });
    Object.defineProperty(window.URL, 'revokeObjectURL', {
      value: vi.fn(),
      configurable: true,
    });
    const clickSpy = vi
      .spyOn(HTMLAnchorElement.prototype, 'click')
      .mockImplementation(() => {});

    render(wrap(<SellerStatementsClient />));
    fireEvent.click(screen.getByTestId('statements-download-csv'));

    await waitFor(() =>
      expect(downloadCsvMock).toHaveBeenCalledWith(STORE_ID, {
        from: '2026-05-01',
        to: '2026-05-14',
      }),
    );
    clickSpy.mockRestore();
  });

  it('clicking Apply writes from/to params and the resulting URL would refetch with them', async () => {
    listForStoreMock.mockResolvedValue(paginatedResponse([]));
    render(wrap(<SellerStatementsClient />));
    await waitFor(() => expect(listForStoreMock).toHaveBeenCalled());

    fireEvent.change(screen.getByTestId('statements-from'), {
      target: { value: '2026-05-01' },
    });
    fireEvent.change(screen.getByTestId('statements-to'), {
      target: { value: '2026-05-14' },
    });
    fireEvent.click(screen.getByTestId('statements-apply-range'));

    expect(replace).toHaveBeenCalledWith(
      '/seller/statements?from=2026-05-01&to=2026-05-14',
    );
    // The hook reads from URL: the next render with these params will re-call
    // listForStore as covered by the URL→listForStore test above.
    expect(currentParams.get('from')).toBe('2026-05-01');
    expect(currentParams.get('to')).toBe('2026-05-14');
  });
});
