import { afterEach, describe, expect, test, vi } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';

vi.mock('$lib/branding', () => ({
  pageTitle: (s: string) => `${s} · Minstrel`
}));

vi.mock('$app/navigation', () => ({
  goto: vi.fn()
}));

vi.mock('$lib/auth/store.svelte', () => ({
  register: vi.fn(),
  user: { value: null }
}));

import RegisterPage from './+page.svelte';
import { register } from '$lib/auth/store.svelte';
import { goto } from '$app/navigation';

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

describe('/register page', () => {
  test('renders username, password, confirm password, and invite token fields', () => {
    render(RegisterPage);
    expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/^password$/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/confirm password/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/invite token/i)).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /create account/i })).toBeInTheDocument();
  });

  test('rejects mismatched passwords without calling register', async () => {
    render(RegisterPage);
    await fireEvent.input(screen.getByLabelText(/username/i), { target: { value: 'newuser' } });
    await fireEvent.input(screen.getByLabelText(/^password$/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/confirm password/i), { target: { value: 'differentpw' } });
    await fireEvent.click(screen.getByRole('button', { name: /create account/i }));
    expect(register).not.toHaveBeenCalled();
    expect(screen.getByRole('alert').textContent).toMatch(/do not match/i);
  });

  test('calls register with form values and redirects on success', async () => {
    (register as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
    render(RegisterPage);
    await fireEvent.input(screen.getByLabelText(/username/i), { target: { value: 'newuser' } });
    await fireEvent.input(screen.getByLabelText(/^password$/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/confirm password/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/invite token/i), { target: { value: 'abc123' } });
    await fireEvent.click(screen.getByRole('button', { name: /create account/i }));
    await waitFor(() => {
      expect(register).toHaveBeenCalledWith(expect.objectContaining({
        username: 'newuser',
        password: 'abcd1234',
        inviteToken: 'abc123',
      }));
    });
    await waitFor(() => {
      expect(goto).toHaveBeenCalledWith('/', { replaceState: true });
    });
  });

  test('shows error message for invite_invalid code', async () => {
    (register as ReturnType<typeof vi.fn>).mockRejectedValue({ code: 'invite_invalid' });
    render(RegisterPage);
    await fireEvent.input(screen.getByLabelText(/username/i), { target: { value: 'newuser' } });
    await fireEvent.input(screen.getByLabelText(/^password$/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/confirm password/i), { target: { value: 'abcd1234' } });
    await fireEvent.click(screen.getByRole('button', { name: /create account/i }));
    await waitFor(() => {
      expect(screen.getByRole('alert').textContent).toMatch(/invite token is invalid/i);
    });
  });

  test('shows error message for username_taken code', async () => {
    (register as ReturnType<typeof vi.fn>).mockRejectedValue({ code: 'username_taken' });
    render(RegisterPage);
    await fireEvent.input(screen.getByLabelText(/username/i), { target: { value: 'existinguser' } });
    await fireEvent.input(screen.getByLabelText(/^password$/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/confirm password/i), { target: { value: 'abcd1234' } });
    await fireEvent.click(screen.getByRole('button', { name: /create account/i }));
    await waitFor(() => {
      expect(screen.getByRole('alert').textContent).toMatch(/username is already taken/i);
    });
  });

  test('shows error message for invite_required code', async () => {
    (register as ReturnType<typeof vi.fn>).mockRejectedValue({ code: 'invite_required' });
    render(RegisterPage);
    await fireEvent.input(screen.getByLabelText(/username/i), { target: { value: 'newuser' } });
    await fireEvent.input(screen.getByLabelText(/^password$/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/confirm password/i), { target: { value: 'abcd1234' } });
    await fireEvent.click(screen.getByRole('button', { name: /create account/i }));
    await waitFor(() => {
      expect(screen.getByRole('alert').textContent).toMatch(/invite token/i);
    });
  });

  test('submit button disables and marks aria-busy while pending', async () => {
    let release!: () => void;
    (register as ReturnType<typeof vi.fn>).mockReturnValue(
      new Promise<void>((resolve) => { release = () => resolve(); })
    );
    render(RegisterPage);
    await fireEvent.input(screen.getByLabelText(/username/i), { target: { value: 'newuser' } });
    await fireEvent.input(screen.getByLabelText(/^password$/i), { target: { value: 'abcd1234' } });
    await fireEvent.input(screen.getByLabelText(/confirm password/i), { target: { value: 'abcd1234' } });
    await fireEvent.click(screen.getByRole('button', { name: /create account/i }));
    const btn = screen.getByRole('button', { name: /creating account/i });
    expect(btn).toBeDisabled();
    expect(btn).toHaveAttribute('aria-busy', 'true');
    release();
    await waitFor(() => expect(screen.getByRole('button', { name: /create account/i })).not.toBeDisabled());
  });

  test('login page has link to register', () => {
    // The login page symmetrically has a "Register" link - tested here via the
    // register page having a "Sign in" link back to /login.
    render(RegisterPage);
    const link = screen.getByRole('link', { name: /sign in/i });
    expect(link).toHaveAttribute('href', '/login');
  });
});
