import { act, fireEvent, render, screen } from '@testing-library/react';
import { useEffect, useState } from 'react';
import { QueryClient, QueryClientProvider, useQuery, useQueryClient } from '@tanstack/react-query';
import { beforeEach, expect, it, vi } from 'vitest';
import type { AuthUser } from '@alqove/types';
import type { Membership } from '@alqove/api-client';
import { useAuthStore } from '@/stores/auth';
import { useTimekeepingGuard } from '@/hooks/use-timekeeping';
import SellerLayout from '../layout';
import StaffLayout from '../../(staff)/layout';

const mocks = vi.hoisted(() => ({ myMemberships: vi.fn(), replace: vi.fn() }));
vi.mock('next/navigation', () => ({ usePathname: () => '/seller', useRouter: () => ({ replace: mocks.replace }) }));
vi.mock('@/lib/api', () => ({ api: { team: { myMemberships: mocks.myMemberships } } }));
vi.mock('@/components/seller/seller-top-bar', () => ({ SellerTopBar: () => null }));
const user = { id: 'same-user' } as AuthUser;
const memberships: Membership[] = ['a', 'b'].map(store_id => ({ id: store_id, store_id, role: 'owner', capabilities: ['store.admin'], is_exempt: false }));
beforeEach(async () => {
  vi.resetAllMocks();
  useAuthStore.getState().clearAuth();
  mocks.myMemberships.mockResolvedValue({ data: memberships });
  useAuthStore.getState().setAuth(user, 'original-token');
  await useAuthStore.getState().refreshMemberships();
});
it.each([
  ['seller', 'A → B → A'], ['seller', 'same-user token replacement'],
  ['staff', 'A → B → A'], ['staff', 'same-user token replacement'],
] as const)('%s shell replaces draft, cache, and mounted guards after batched %s', async (shell, transition) => {
  const clients: QueryClient[] = [];
  const guards: ReturnType<typeof useTimekeepingGuard>[] = [];
  function Editor() {
    const [draft, setDraft] = useState('');
    const qc = useQueryClient();
    const guard = useTimekeepingGuard('a');
    const [mountedGuard] = useState(guard);
    useEffect(() => { clients.push(qc); guards.push(mountedGuard); }, [qc, mountedGuard]);
    return <><input aria-label="Draft" value={draft} onChange={event => { setDraft(event.target.value); qc.setQueryData(['private'], event.target.value); }} />
      <span data-testid="cache">{qc.getQueryData<string>(['private']) ?? 'empty'}</span></>;
  }
  const Layout = shell === 'seller' ? SellerLayout : StaffLayout;
  render(<QueryClientProvider client={new QueryClient()}><Layout><Editor /></Layout></QueryClientProvider>);
  fireEvent.change(screen.getByLabelText('Draft'), { target: { value: 'Private old draft' } });
  const originalClient = clients[0];
  const originalGuard = guards[0];
  await act(async () => {
    if (transition === 'A → B → A') {
      useAuthStore.getState().selectStore('b');
      useAuthStore.getState().selectStore('a');
    } else {
      useAuthStore.getState().setAuth(user, 'replacement-token');
      await useAuthStore.getState().refreshMemberships();
    }
  });
  expect(screen.getByLabelText('Draft')).toHaveValue('');
  expect(screen.getByTestId('cache')).toHaveTextContent('empty');
  expect(clients.at(-1)).not.toBe(originalClient);
  expect(originalGuard.current()).toBe(false);
  expect(guards.at(-1)!.current()).toBe(true);
  expect(() => guards.at(-1)!.assert()).not.toThrow();
});

it('staff shell provides a real query client without an outer provider', async () => {
  const fetchClock = vi.fn().mockResolvedValue({ status: 'clocked-out' });
  function Clock() {
    const query = useQuery({ queryKey: ['clock-smoke'], queryFn: fetchClock });
    return <p>{query.data?.status ?? 'Fetching clock'}</p>;
  }
  render(<StaffLayout><Clock /></StaffLayout>);
  expect(await screen.findByText('clocked-out')).toBeInTheDocument();
  expect(fetchClock).toHaveBeenCalledOnce();
});
