import type { ComponentProps, ReactNode } from 'react';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { api } from '@spoon/backend/convex/_generated/api.js';

import { NotificationPreferencesPanel } from '../../src/components/settings/notification-preferences-panel';

// Stable sentinels: the real Convex `api` proxy hands back a fresh reference on
// every access, so `===` comparisons in the mocks below would never match.
vi.mock('@spoon/backend/convex/_generated/api.js', () => ({
  api: {
    notifications: {
      getPreferences: 'notifications:getPreferences',
      updatePreferences: 'notifications:updatePreferences',
    },
  },
}));

const { mockUpdatePreferences, mockUseMutation, mockUseQuery } = vi.hoisted(
  () => ({
    mockUpdatePreferences: vi.fn(),
    mockUseMutation: vi.fn(),
    mockUseQuery: vi.fn(),
  }),
);

vi.mock('convex/react', () => ({
  useMutation: mockUseMutation,
  useQuery: mockUseQuery,
}));

vi.mock('sonner', () => ({
  toast: { error: vi.fn(), success: vi.fn() },
}));

// Pass-through UI primitives so the switches are deterministically present and
// clickable under jsdom (Radix pointer behavior is flaky here). We exercise the
// panel's logic, not the design-system components.
vi.mock('@spoon/ui', () => ({
  Card: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
  Label: ({ children }: { children?: ReactNode }) => <label>{children}</label>,
  Switch: ({
    checked,
    onCheckedChange,
    disabled,
    ...props
  }: {
    checked?: boolean;
    onCheckedChange?: (value: boolean) => void;
    disabled?: boolean;
  } & ComponentProps<'button'>) => (
    <button
      role='switch'
      aria-checked={checked}
      disabled={disabled}
      onClick={() => onCheckedChange?.(!checked)}
      {...props}
    />
  ),
}));

const prefs = {
  emailEnabled: true,
  emailMaintenance: true,
  emailAgentTurnFinished: true,
  emailAgentNeedsInput: false,
  emailSyncFailed: true,
  emailConnectionReauth: true,
};

const seed = () => {
  mockUpdatePreferences.mockResolvedValue(undefined);
  mockUseMutation.mockReturnValue(mockUpdatePreferences);
  mockUseQuery.mockImplementation((reference: unknown) =>
    reference === api.notifications.getPreferences ? prefs : undefined,
  );
};

afterEach(() => {
  cleanup();
  vi.clearAllMocks();
});

describe('NotificationPreferencesPanel', () => {
  it('renders toggle state from the query values', () => {
    seed();
    render(<NotificationPreferencesPanel />);

    const needsInput = screen.getByRole('switch', {
      name: /needs your input/i,
    });
    expect(needsInput).toHaveAttribute('aria-checked', 'false');

    const master = screen.getByRole('switch', { name: /email notifications/i });
    expect(master).toHaveAttribute('aria-checked', 'true');
  });

  it('calls updatePreferences with the flipped field when a toggle changes', () => {
    seed();
    render(<NotificationPreferencesPanel />);

    fireEvent.click(
      screen.getByRole('switch', { name: /sync fails/i }),
    );
    expect(mockUpdatePreferences).toHaveBeenCalledWith({
      emailSyncFailed: false,
    });
  });

  it('does not render toggles until the query resolves', () => {
    mockUseMutation.mockReturnValue(mockUpdatePreferences);
    mockUseQuery.mockReturnValue(undefined);
    render(<NotificationPreferencesPanel />);
    expect(screen.queryByRole('switch')).not.toBeInTheDocument();
  });
});
