import { act, fireEvent, render, screen } from '@testing-library/react';
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 InvitationAcceptPage from '../page';

const { replace, accept, myMemberships } = vi.hoisted(() => ({ replace: vi.fn(), accept: vi.fn(), myMemberships: vi.fn() }));
vi.mock('next/navigation', () => ({ useRouter: () => ({ replace }) }));
vi.mock('@/lib/api', () => ({ api: { team: { myMemberships, invitations: { accept } } } }));
const user = { id: 'u1', email: 'invited@example.com' } as AuthUser;
const member = (store: string): Membership => ({ id: `m-${store}`, store_id: store, role: 'sales', capabilities: ['team.view'], is_exempt: false });
const invitationKey = 'alqove.pending-invitation';
function deferred<T>() {
  let resolve!: (value: T) => void;
  const promise = new Promise<T>(r => { resolve = r; });
  return { promise, resolve };
}
beforeEach(async () => {
  vi.resetAllMocks();
  useAuthStore.getState().clearAuth();
  localStorage.clear();
  sessionStorage.clear();
  window.history.replaceState(null, '', '/invitations/accept?token=fixture-invitation');
  useAuthStore.getState().setAuth(user, 'fixture-session');
  myMemberships.mockResolvedValue({ data: [member('s1')] });
  await useAuthStore.getState().refreshMemberships();
});

it('does not revive an old acceptance after clearAuth and reentry with identical user and token', async () => {
  const pending = deferred<{ data: { store_id: string } }>();
  accept.mockReturnValue(pending.promise);
  render(<InvitationAcceptPage />);
  fireEvent.click(await screen.findByRole('button', { name: 'Accept invitation' }));
  expect(accept).toHaveBeenCalledExactlyOnceWith('fixture-invitation');
  await act(async () => {
    useAuthStore.getState().clearAuth();
    useAuthStore.getState().setAuth(user, 'fixture-session');
    await useAuthStore.getState().refreshMemberships();
  });
  myMemberships.mockClear();
  await act(async () => { pending.resolve({ data: { store_id: 's1' } }); });
  expect(sessionStorage.getItem(invitationKey)).toBe('fixture-invitation');
  expect(replace).not.toHaveBeenCalled();
  expect(myMemberships).not.toHaveBeenCalled();
  expect(screen.getByRole('button', { name: 'Accept invitation' })).toBeEnabled();
});

it('does not revive accepted store-access retry after same-credential session reentry', async () => {
  accept.mockResolvedValue({ data: { store_id: 's1' } });
  myMemberships.mockResolvedValueOnce({ data: [] });
  render(<InvitationAcceptPage />);
  fireEvent.click(await screen.findByRole('button', { name: 'Accept invitation' }));
  expect(await screen.findByRole('alert')).toHaveTextContent(/could not verify store access/i);
  await act(async () => {
    useAuthStore.getState().clearAuth();
    useAuthStore.getState().setAuth(user, 'fixture-session');
    myMemberships.mockResolvedValue({ data: [member('s2'), member('s1')] });
    await useAuthStore.getState().refreshMemberships();
  });
  myMemberships.mockClear();
  await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'Retry store access' })); });
  expect(replace).not.toHaveBeenCalled();
  expect(myMemberships).not.toHaveBeenCalled();
  expect(useAuthStore.getState().selectedStoreId).toBe('s2');
  expect(screen.getByRole('alert')).toHaveTextContent(/account changed/i);
  expect(accept).toHaveBeenCalledOnce();
});

it('does not navigate when identical credentials reenter during acceptance membership discovery', async () => {
  const discovery = deferred<{ data: Membership[] }>();
  accept.mockResolvedValue({ data: { store_id: 's1' } });
  myMemberships.mockReturnValueOnce(discovery.promise);
  render(<InvitationAcceptPage />);
  await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'Accept invitation' })); });
  expect(screen.getByRole('button', { name: 'Please wait…' })).toBeDisabled();
  expect(sessionStorage.getItem(invitationKey)).toBeNull();
  await act(async () => {
    useAuthStore.getState().clearAuth();
    useAuthStore.getState().setAuth(user, 'fixture-session');
    myMemberships.mockResolvedValue({ data: [member('s2'), member('s1')] });
    await useAuthStore.getState().refreshMemberships();
  });
  sessionStorage.setItem(invitationKey, 'replacement-invitation');
  await act(async () => { discovery.resolve({ data: [member('s1')] }); });
  expect(replace).not.toHaveBeenCalled();
  expect(useAuthStore.getState().selectedStoreId).toBe('s2');
  expect(sessionStorage.getItem(invitationKey)).toBe('replacement-invitation');
  expect(screen.getByRole('button', { name: 'Retry store access' })).toBeEnabled();
});

it('completes acceptance when discovery and invited-store selection change command generation', async () => {
  const pending = deferred<{ data: { store_id: string } }>();
  accept.mockReturnValue(pending.promise);
  const originGeneration = useAuthStore.getState().commandGeneration;
  render(<InvitationAcceptPage />);
  fireEvent.click(await screen.findByRole('button', { name: 'Accept invitation' }));
  // Discovery drops the formerly selected s1 and first selects s2; acceptance selects s3.
  myMemberships.mockResolvedValue({ data: [member('s2'), member('s3')] });
  await act(async () => { pending.resolve({ data: { store_id: 's3' } }); });
  expect(useAuthStore.getState().commandGeneration).toBeGreaterThan(originGeneration);
  expect(useAuthStore.getState().selectedStoreId).toBe('s3');
  expect(sessionStorage.getItem(invitationKey)).toBeNull();
  expect(replace).toHaveBeenCalledExactlyOnceWith('/staff');
  expect(accept).toHaveBeenCalledOnce();
});
