import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { MutationCache, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { afterEach, 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 { ItemForm } from '../item-form';

const mocks = vi.hoisted(() => ({ update: vi.fn(), create: vi.fn(), publish: vi.fn(), remove: vi.fn(), relist: vi.fn(), get: vi.fn(), replace: vi.fn(), push: vi.fn(), myMemberships: vi.fn() }));
vi.mock('next/navigation', () => ({ useRouter: () => ({ replace: mocks.replace, push: mocks.push }) }));
vi.mock('@/lib/api', () => ({ api: { items: mocks, team: { myMemberships: mocks.myMemberships } } }));
vi.mock('@/components/seller/image-uploader', () => ({ ImageUploader: () => null }));
vi.mock('@/components/seller/category-autocomplete', () => ({ CategoryAutocomplete: ({ onChange }: { onChange: (id: number) => void }) => <button onClick={() => onChange(1)}>Choose category</button> }));
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 }));
const detail = { id: 'item', title: 'Old title', description: null, category: { id: 1, name: 'Jacket' }, brand: null, size: null, condition: 'GUC', colors: [], price: 1000, status: 'draft', images: [{ id: 1, url: 'image', thumb_url: 'image', order_column: 1 }] };
beforeEach(async () => {
  vi.resetAllMocks();
  useAuthStore.getState().clearAuth();
  mocks.myMemberships.mockResolvedValue({ data: memberships });
  mocks.get.mockResolvedValue({ data: detail });
  mocks.update.mockResolvedValue({ data: detail });
  useAuthStore.getState().setAuth(user, 'original-token');
  await useAuthStore.getState().refreshMemberships();
});
afterEach(() => vi.useRealTimers());
function setup(itemId: string | null = 'item', status = 'draft', onMutate?: () => Promise<void>) {
  const qc = new QueryClient({ mutationCache: new MutationCache({ onMutate }), defaultOptions: { queries: { retry: false, staleTime: Infinity }, mutations: { retry: false } } });
  qc.setQueryData(['seller-item', 'a', 'item'], { data: { ...detail, status } });
  return { qc, ...render(<QueryClientProvider client={qc}><ItemForm storeId="a" itemId={itemId ?? undefined} /></QueryClientProvider>) };
}
it('cancels listing autosave when the editor unmounts', async () => {
  const view = setup();
  vi.useFakeTimers();
  fireEvent.change(screen.getByLabelText('Title'), { target: { value: 'Old draft' } });
  fireEvent.blur(screen.getByLabelText('Title'));
  view.unmount();
  await act(async () => { await vi.advanceTimersByTimeAsync(1000); });
  expect(mocks.update).not.toHaveBeenCalled();
});

it.each(['same-user token replacement', 'A → B → A', 'logout'])('drops queued autosave after %s even without unmount', async transition => {
  setup();
  vi.useFakeTimers();
  fireEvent.change(screen.getByLabelText('Title'), { target: { value: 'Old draft' } });
  fireEvent.blur(screen.getByLabelText('Title'));
  await act(async () => {
    if (transition === 'A → B → A') {
      useAuthStore.getState().selectStore('b');
      useAuthStore.getState().selectStore('a');
    } else if (transition === 'logout') {
      useAuthStore.getState().clearAuth();
    } else {
      useAuthStore.getState().setAuth(user, 'replacement-token');
      await useAuthStore.getState().refreshMemberships();
    }
    await vi.advanceTimersByTimeAsync(1000);
  });
  expect(mocks.update).not.toHaveBeenCalled();
});

function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>(r => { resolve = r; });
  return { promise, resolve };
}
it.each(['same-user token replacement', 'A → B → A', 'logout', 'unmount'])('does not navigate on late create success after %s', async transition => {
  const pending = deferred<{ data: typeof detail }>();
  mocks.create.mockReturnValue(pending.promise);
  const view = setup(null);
  fireEvent.change(screen.getByLabelText('Title'), { target: { value: 'Old draft' } });
  fireEvent.click(screen.getByRole('button', { name: 'Category' }));
  fireEvent.click(screen.getByRole('button', { name: 'Save draft' }));
  await waitFor(() => expect(mocks.create).toHaveBeenCalledOnce());
  await act(async () => {
    if (transition === 'A → B → A') {
      useAuthStore.getState().selectStore('b');
      useAuthStore.getState().selectStore('a');
    } else if (transition === 'logout') {
      useAuthStore.getState().clearAuth();
    } else if (transition === 'unmount') {
      view.unmount();
    } else {
      useAuthStore.getState().setAuth(user, 'replacement-token');
      await useAuthStore.getState().refreshMemberships();
    }
    pending.resolve({ data: detail });
  });
  expect(mocks.replace).not.toHaveBeenCalled();
  expect(mocks.push).not.toHaveBeenCalled();
});

it.each([
  ['publish', 'draft', 'Publish'], ['remove', 'active', 'Remove'], ['relist', 'removed', 'Relist'],
] as const)('blocks queued %s after A → B → A', async (method, status, button) => {
  const pending = deferred<void>();
  const prerequisite = vi.fn(() => pending.promise);
  setup('item', status, prerequisite);
  fireEvent.click(screen.getByRole('button', { name: button }));
  await waitFor(() => expect(prerequisite).toHaveBeenCalledOnce());
  await act(async () => {
    useAuthStore.getState().selectStore('b');
    useAuthStore.getState().selectStore('a');
    pending.resolve();
  });
  expect(mocks[method]).not.toHaveBeenCalled();
});

it('does not show late autosave success in a replacement session', async () => {
  const pending = deferred<{ data: typeof detail }>();
  mocks.update.mockReturnValue(pending.promise);
  const { qc } = setup();
  const invalidate = vi.spyOn(qc, 'invalidateQueries');
  vi.useFakeTimers();
  fireEvent.blur(screen.getByLabelText('Title'));
  await act(async () => { await vi.advanceTimersByTimeAsync(1000); });
  expect(mocks.update).toHaveBeenCalledOnce();
  await act(async () => {
    useAuthStore.getState().setAuth(user, 'replacement-token');
    await useAuthStore.getState().refreshMemberships();
    pending.resolve({ data: detail });
    await vi.advanceTimersByTimeAsync(1);
  });
  expect(screen.queryByText(/Saved at/)).not.toBeInTheDocument();
  expect(invalidate).toHaveBeenCalledWith({ queryKey: ['seller-item', 'a', 'item'], refetchType: 'none' });
});

it.each([
  ['publish', 'draft', 'Publish', 'Draft'], ['remove', 'active', 'Remove', 'Published'], ['relist', 'removed', 'Relist', 'Removed'],
] as const)('does not show late %s status after A → B → A', async (method, status, button, originalLabel) => {
  const pending = deferred<{ data: typeof detail }>();
  mocks[method].mockReturnValue(pending.promise);
  setup('item', status);
  fireEvent.click(screen.getByRole('button', { name: button }));
  await waitFor(() => expect(mocks[method]).toHaveBeenCalledOnce());
  await act(async () => {
    useAuthStore.getState().selectStore('b');
    useAuthStore.getState().selectStore('a');
    pending.resolve({ data: detail });
  });
  expect(screen.getByText(originalLabel)).toBeInTheDocument();
});

it('autosaves the changed condition rather than the previous render snapshot', async () => {
  setup();
  vi.useFakeTimers();
  fireEvent.change(screen.getByLabelText('Condition'), { target: { value: 'NWT' } });
  await act(async () => { await vi.advanceTimersByTimeAsync(1000); });
  expect(mocks.update).toHaveBeenCalledWith('a', 'item', expect.objectContaining({ condition: 'NWT' }));
});
