import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { api } from '@/lib/api';
import { SellerScheduleClient } from '../seller-schedule-client';
import { useSelectedMembership } from '@/lib/stores/store-context';
import type { Membership, ScheduleWeek, Shift } from '@alqove/api-client';

vi.mock('@/lib/stores/store-context', () => ({ useSelectedMembership: vi.fn(), requireSelectedStore: vi.fn((id: string) => id), captureCommandGeneration: vi.fn(() => 7) }));
vi.mock('@/lib/api', () => ({ api: {
  scheduling: { settings: vi.fn(), week: vi.fn(), createShift: vi.fn(), updateShift: vi.fn(), deleteShift: vi.fn(), copyWeek: vi.fn(), publish: vi.fn() },
  team: { members: { list: vi.fn() }, positions: { list: vi.fn() } },
} }));
const membership: Membership = { id: 'm1', store_id: 's1', role: 'manager', is_exempt: false, capabilities: ['schedule.view', 'schedule.manage', 'schedule.publish', 'schedule.view_drafts', 'team.view'] };
const shift: Shift = { id: 'shift1', store_id: 's1', store_membership_id: 'm1', member_name: 'Ada', position_id: null, position_name: null, starts_at: '2026-09-07T16:00:00Z', ends_at: '2026-09-07T23:00:00Z', notes: 'Front desk', published_at: null, revision: '2026-09-06T12:13:14.123456Z' };
const week: ScheduleWeek = { week_start: '2026-09-07', starts_at: '2026-09-07T07:00:00Z', ends_at: '2026-09-14T07:00:00Z', timezone: 'America/Los_Angeles', shifts: [shift], publication: null };
function mount() {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false } } });
  return render(<QueryClientProvider client={qc}><SellerScheduleClient /></QueryClientProvider>);
}
beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(api.scheduling.settings).mockResolvedValue({ data: { timezone: 'America/Los_Angeles', work_week_start_day: 'mon', work_week_start_time: '04:00', payroll_rounding_mode: 'none', payroll_rounding_increment_minutes: 1, clock_in_early_minutes: 0, clock_in_late_minutes: 0, clock_out_late_minutes: 0 } });
  vi.mocked(useSelectedMembership).mockReturnValue(membership);
  vi.mocked(api.scheduling.week).mockResolvedValue({ data: week });
  vi.mocked(api.team.members.list).mockResolvedValue({ data: [{ id: 'm1', user_id: 'u1', name: 'Ada', role: 'sales', status: 'active' }] });
  vi.mocked(api.team.positions.list).mockResolvedValue({ data: [] });
});
afterEach(() => vi.useRealTimers());
describe('seller scheduling', () => {
  it('starts in the store-local current week rather than the UTC or browser date', async () => {
    vi.setSystemTime(new Date('2026-09-07T02:00:00Z'));
    mount();
    await waitFor(() => expect(api.scheduling.week).toHaveBeenCalledWith('s1', '2026-09-06'));
  });
  it('requires copy preview before commit, clears it when dates change, and confirms publishing', async () => {
    vi.mocked(api.scheduling.copyWeek).mockResolvedValue({ data: { preview: true, source_week_start: '2026-09-07', target_week_start: '2026-09-14', shift_count: 1, copied: 0, conflict_count: 0, conflicts: [], shifts: [{ ...shift, id: null, revision: null, starts_at: '2026-09-14T16:00:00Z', ends_at: '2026-09-14T23:00:00Z' }] } } as never);
    vi.mocked(api.scheduling.publish).mockResolvedValue({ data: week });
    mount();
    await userEvent.click(await screen.findByRole('button', { name: 'Copy week' }));
    expect(screen.queryByRole('button', { name: 'Commit copy' })).not.toBeInTheDocument();
    await userEvent.click(screen.getByRole('button', { name: 'Preview copy' }));
    expect(await screen.findByText(/1 shift.*0 conflict/i)).toBeInTheDocument();
    expect(api.scheduling.copyWeek).toHaveBeenLastCalledWith('s1', { source_week_start: '2026-09-07', target_week_start: '2026-09-14', preview: true });
    fireEvent.change(screen.getByLabelText('Target week'), { target: { value: '2026-09-21' } });
    expect(screen.queryByRole('button', { name: 'Commit copy' })).not.toBeInTheDocument();
    await userEvent.click(screen.getByRole('button', { name: 'Preview copy' }));
    await userEvent.click(await screen.findByRole('button', { name: 'Commit copy' }));
    await waitFor(() => expect(api.scheduling.copyWeek).toHaveBeenLastCalledWith('s1', { source_week_start: '2026-09-07', target_week_start: '2026-09-21', preview: false }));
    expect(await screen.findByText(/Copy committed.*2026-09-14/i)).toBeInTheDocument();
    await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
    await userEvent.click(screen.getByRole('button', { name: 'Publish week' }));
    expect(api.scheduling.publish).not.toHaveBeenCalled();
    await userEvent.click(screen.getByRole('button', { name: 'Confirm publish' }));
    await waitFor(() => expect(api.scheduling.publish).toHaveBeenCalledWith('s1', '2026-09-07'));
  });
  it('does not grant publishing or settings merely for a manager role', async () => {
    vi.mocked(useSelectedMembership).mockReturnValue({ ...membership, capabilities: ['schedule.view', 'schedule.manage', 'team.view'] });
    mount();
    await screen.findByRole('table');
    expect(screen.queryByRole('button', { name: 'Publish week' })).not.toBeInTheDocument();
    expect(screen.queryByRole('link', { name: 'Schedule settings' })).not.toBeInTheDocument();
  });
  it('creates a shift from store-local inputs then edits with the exact revision and deletes explicitly', async () => {
    vi.mocked(api.scheduling.createShift).mockResolvedValue({ data: shift });
    vi.mocked(api.scheduling.updateShift).mockResolvedValue({ data: shift });
    vi.mocked(api.scheduling.deleteShift).mockResolvedValue({ data: { deleted: true } });
    mount();
    await userEvent.click(await screen.findByRole('button', { name: 'Add shift' }));
    fireEvent.change(screen.getByLabelText('Starts (store time)'), { target: { value: '2026-09-07T09:00' } });
    fireEvent.change(screen.getByLabelText('Ends (store time)'), { target: { value: '2026-09-07T17:00' } });
    await userEvent.selectOptions(screen.getByLabelText('Employee'), 'm1');
    await userEvent.click(screen.getByRole('button', { name: 'Save shift' }));
    await waitFor(() => expect(api.scheduling.createShift).toHaveBeenCalledWith('s1', { store_membership_id: 'm1', position_id: null, starts_at: '2026-09-07T16:00:00.000Z', ends_at: '2026-09-08T00:00:00.000Z', notes: null }));
    await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
    await userEvent.click(screen.getByRole('button', { name: /Edit Ada/ }));
    await userEvent.type(screen.getByLabelText('Notes'), ' updated');
    await userEvent.click(screen.getByRole('button', { name: 'Save shift' }));
    await waitFor(() => expect(api.scheduling.updateShift).toHaveBeenCalledWith('s1', 'shift1', expect.objectContaining({ revision: shift.revision, notes: 'Front desk updated' })));
    await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
    await userEvent.click(screen.getByRole('button', { name: /Edit Ada/ }));
    await userEvent.click(screen.getByRole('button', { name: 'Delete shift' }));
    expect(api.scheduling.deleteShift).not.toHaveBeenCalled();
    await userEvent.click(screen.getByRole('button', { name: 'Confirm delete' }));
    await waitFor(() => expect(api.scheduling.deleteShift).toHaveBeenCalledWith('s1', 'shift1', shift.revision));
  });
  it('keeps unsaved inputs on overlap and stale revision feedback, with explicit reload', async () => {
    vi.mocked(api.scheduling.updateShift).mockRejectedValueOnce({ message: 'This member already has an overlapping shift.' }).mockRejectedValueOnce({ message: 'Shift changed. Reload before editing.' });
    mount();
    await userEvent.click(await screen.findByRole('button', { name: /Edit Ada/ }));
    await userEvent.type(screen.getByLabelText('Notes'), ' keep me');
    await userEvent.click(screen.getByRole('button', { name: 'Save shift' }));
    expect(await screen.findByRole('alert')).toHaveTextContent(/overlapping shift/i);
    expect(screen.getByLabelText('Notes')).toHaveValue('Front desk keep me');
    await userEvent.click(screen.getByRole('button', { name: 'Save shift' }));
    await waitFor(() => expect(screen.getByRole('alert')).toHaveTextContent(/reload/i));
    expect(screen.getByRole('button', { name: 'Save shift' })).toBeDisabled();
    await userEvent.click(screen.getByRole('button', { name: 'Reload latest schedule' }));
    await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument());
  });
  it('renders employees by store-local days with draft labels and week navigation', async () => {
    mount();
    expect(await screen.findByRole('table', { name: /weekly schedule/i })).toBeInTheDocument();
    expect(screen.getByText('America/Los_Angeles')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /Ada.*09:00.*16:00/i })).toHaveTextContent('Draft');
    await userEvent.click(screen.getByRole('button', { name: 'Next week' }));
    await waitFor(() => expect(api.scheduling.week).toHaveBeenLastCalledWith('s1', '2026-09-14'));
  });
  it('fails closed without selected membership', () => {
    vi.mocked(useSelectedMembership).mockReturnValue(undefined);
    mount();
    expect(api.scheduling.week).not.toHaveBeenCalled();
    expect(screen.queryByRole('button', { name: 'Add shift' })).not.toBeInTheDocument();
  });
});
