import { render, screen, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import type { Membership, StoreMember } from '@alqove/api-client';
import { useAuthStore } from '@/stores/auth';
import { SellerTeamClient } from '../seller-team-client';

vi.mock('@/stores/auth', () => ({
  useAuthStore: vi.fn(),
  selectMembershipFor:
    (storeId: string | null | undefined) =>
    (state: { memberships: Membership[] | null }) =>
      storeId ? state.memberships?.find((m) => m.store_id === storeId) : undefined,
}));

const membersList = vi.fn();
const positionsList = vi.fn();
const invitationsList = vi.fn();
vi.mock('@/lib/api', () => ({
  api: {
    team: {
      members: { list: (...a: unknown[]) => membersList(...a) },
      positions: { list: (...a: unknown[]) => positionsList(...a) },
      invitations: { list: (...a: unknown[]) => invitationsList(...a) },
    },
  },
}));

const STORE_ID = 'store-1';

const OWNER_CAPS = [
  'store.admin', 'team.view', 'team.manage', 'pay.view', 'pay.manage',
  'schedule.view', 'schedule.view_drafts', 'schedule.manage', 'schedule.publish',
  'punches.manage', 'timesheets.approve', 'payroll.export', 'settings.schedule',
] as Membership['capabilities'];

const SALES_CAPS = ['team.view', 'schedule.view'] as Membership['capabilities'];

function membership(role: Membership['role'], capabilities: Membership['capabilities']): Membership {
  return { id: `m-${role}`, store_id: STORE_ID, role, capabilities, is_exempt: false };
}

function member(overrides: Partial<StoreMember> & { id: string }): StoreMember {
  return { user_id: `u-${overrides.id}`, role: 'sales', status: 'active', name: 'Pat', ...overrides };
}

function mockViewer(m: Membership) {
  vi.mocked(useAuthStore).mockImplementation(((selector?: (s: unknown) => unknown) => {
    const state = {
      user: { id: 'u1', name: 'Test', store_id: 'legacy-not-selected' },
        selectedStoreId: STORE_ID,
      memberships: [m],
      isLoading: false,
    };
    return selector ? selector(state) : state;
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
  }) as any);
}

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

describe('SellerTeamClient', () => {
  beforeEach(() => {
    membersList.mockReset();
    positionsList.mockReset();
    invitationsList.mockReset();
    positionsList.mockResolvedValue({ data: [] });
    invitationsList.mockResolvedValue({ data: [] });
  });

  it('renders loading skeleton initially', () => {
    mockViewer(membership('owner', OWNER_CAPS));
    membersList.mockReturnValue(new Promise(() => {}));
    render(wrap(<SellerTeamClient />));
    expect(screen.getByTestId('team-loading')).toBeInTheDocument();
  });

  it('renders error state on API failure', async () => {
    mockViewer(membership('owner', OWNER_CAPS));
    membersList.mockRejectedValue(new Error('boom'));
    render(wrap(<SellerTeamClient />));
    await waitFor(() => expect(screen.getByTestId('team-error')).toBeInTheDocument());
  });

  it('renders empty state', async () => {
    mockViewer(membership('owner', OWNER_CAPS));
    membersList.mockResolvedValue({ data: [] });
    render(wrap(<SellerTeamClient />));
    await waitFor(() => expect(screen.getByTestId('team-empty')).toBeInTheDocument());
  });

  it('owner sees roster with invite button, pay column and edit actions', async () => {
    mockViewer(membership('owner', OWNER_CAPS));
    membersList.mockResolvedValue({
      data: [
        member({ id: 'a', name: 'Ada', role: 'manager', email: 'ada@example.com', current_pay_rate_cents: 1850 }),
        member({ id: 'b', name: 'Bo', role: 'sales', status: 'terminated' }),
      ],
    });
    render(wrap(<SellerTeamClient />));

    await waitFor(() => expect(screen.getByTestId('team-row-a')).toBeInTheDocument());
    expect(screen.getByTestId('team-invite-button')).toBeInTheDocument();
    expect(screen.getByTestId('team-pay-col')).toBeInTheDocument();
    expect(screen.getByTestId('team-pay-a')).toHaveTextContent('$18.50/hr');
    expect(screen.getByTestId('team-edit-a')).toBeInTheDocument();
    expect(screen.getByText('ada@example.com')).toBeInTheDocument();
    expect(screen.getByTestId('team-row-b')).toHaveTextContent('Terminated');
    expect(membersList).toHaveBeenCalledWith(STORE_ID);
  });

  it('sales viewer sees the roster but no manage/pay affordances', async () => {
    mockViewer(membership('sales', SALES_CAPS));
    membersList.mockResolvedValue({ data: [member({ id: 'a', name: 'Ada', role: 'manager' })] });
    render(wrap(<SellerTeamClient />));

    await waitFor(() => expect(screen.getByTestId('team-row-a')).toBeInTheDocument());
    expect(screen.queryByTestId('team-invite-button')).not.toBeInTheDocument();
    expect(screen.queryByTestId('team-pay-col')).not.toBeInTheDocument();
    expect(screen.queryByTestId('team-edit-a')).not.toBeInTheDocument();
    // Invitations are a team.manage surface; it must not even be fetched.
    expect(invitationsList).not.toHaveBeenCalled();
  });

  it('shows pending invitations with revoke for managers', async () => {
    mockViewer(membership('owner', OWNER_CAPS));
    membersList.mockResolvedValue({ data: [] });
    invitationsList.mockResolvedValue({
      data: [{ id: 'inv-1', store_id: STORE_ID, email: 'new@example.com', role: 'sales', status: 'pending', expires_at: '2026-09-09T00:00:00Z' }],
    });
    render(wrap(<SellerTeamClient />));

    await waitFor(() => expect(screen.getByTestId('team-invitations')).toBeInTheDocument());
    expect(screen.getByTestId('invite-row-inv-1')).toHaveTextContent('new@example.com');
    expect(screen.getByTestId('invite-revoke-inv-1')).toBeInTheDocument();
  });
});
