import { act } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { createMemoryRouter, RouterProvider, type RouteObject } from 'react-router-dom';
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest';

const { apiMock, useSessionMock, updateUserMock } = vi.hoisted(() => ({
  apiMock: vi.fn(),
  useSessionMock: vi.fn(),
  updateUserMock: vi.fn(),
}));

vi.mock('@/lib/api', () => ({
  api: apiMock,
}));

vi.mock('@/lib/auth-client', () => ({
  useSession: useSessionMock,
  authClient: { updateUser: updateUserMock },
}));

import { SettingsPage } from './settings';

let root: Root | null = null;
let container: HTMLDivElement;

beforeAll(() => {
  Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', {
    configurable: true,
    value: true,
  });
});

afterAll(() => {
  Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT');
});

afterEach(async () => {
  await act(async () => {
    root?.unmount();
  });
  document.body.replaceChildren();
  root = null;
  apiMock.mockReset();
  useSessionMock.mockReset();
  updateUserMock.mockReset();
});

async function renderSettingsPage(): Promise<void> {
  const routes: RouteObject[] = [{ path: '/settings', element: <SettingsPage /> }];
  const router = createMemoryRouter(routes, { initialEntries: ['/settings'] });
  container = document.createElement('div');
  document.body.append(container);
  root = createRoot(container);

  await act(async () => {
    root?.render(<RouterProvider router={router} />);
  });
}

function clickButtonByText(text: string): Promise<void> {
  const button = [...container.querySelectorAll('button')].find((candidate) =>
    candidate.textContent?.includes(text),
  );
  if (!button) {
    throw new Error(`Button containing "${text}" not found`);
  }
  return act(async () => {
    button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
  });
}

const session = {
  user: { id: 'u-1', name: 'Test User', email: 'user@example.test', image: null },
};

describe('SettingsPage profile tab', () => {
  it('renders the profile form from the session and saves via authClient', async () => {
    useSessionMock.mockReturnValue({ data: session, isPending: false });
    updateUserMock.mockResolvedValue({});

    await renderSettingsPage();

    const nameInput = container.querySelector<HTMLInputElement>('#profile-name');
    const emailInput = container.querySelector<HTMLInputElement>('#profile-email');
    expect(nameInput?.value).toBe('Test User');
    expect(emailInput?.value).toBe('user@example.test');
    expect(emailInput?.disabled).toBe(true);

    await clickButtonByText('Save changes');

    expect(updateUserMock).toHaveBeenCalledWith({ name: 'Test User', image: null });
    expect(container.textContent).toContain('Saved!');
  });

  it('surfaces an update failure without clearing the form', async () => {
    useSessionMock.mockReturnValue({ data: session, isPending: false });
    updateUserMock.mockResolvedValue({ error: { message: 'name rejected' } });

    await renderSettingsPage();
    await clickButtonByText('Save changes');

    expect(container.textContent).toContain('name rejected');
    expect(container.querySelector<HTMLInputElement>('#profile-name')?.value).toBe('Test User');
  });
});

describe('SettingsPage appearance tab', () => {
  it('loads preferences and posts each changed preference on save', async () => {
    useSessionMock.mockReturnValue({ data: session, isPending: false });
    apiMock.mockImplementation((path: string) =>
      path.startsWith('/api/memory/preferences?')
        ? Promise.resolve([{ key: 'ui.theme', value: 'dark', category: 'appearance' }])
        : Promise.resolve({}),
    );

    await renderSettingsPage();
    await clickButtonByText('Appearance');

    expect(apiMock).toHaveBeenCalledWith('/api/memory/preferences?category=appearance');

    await clickButtonByText('Save changes');

    expect(apiMock).toHaveBeenCalledWith('/api/memory/preferences', {
      method: 'POST',
      body: { key: 'ui.theme', value: 'dark', category: 'appearance', source: 'user' },
    });
  });
});

describe('SettingsPage providers tab', () => {
  it('loads LLM and SSO providers and runs a connection test', async () => {
    useSessionMock.mockReturnValue({ data: session, isPending: false });
    apiMock.mockImplementation((path: string, opts?: { method?: string }) => {
      if (path === '/api/providers' && opts === undefined) {
        return Promise.resolve([
          {
            id: 'ollama',
            name: 'Ollama',
            available: true,
            models: [
              {
                id: 'llama3.2',
                provider: 'ollama',
                name: 'Llama 3.2',
                reasoning: false,
                contextWindow: 128_000,
                maxTokens: 4096,
                inputTypes: ['text'],
                cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
              },
            ],
          },
        ]);
      }
      if (path === '/api/sso/providers') {
        return Promise.resolve([]);
      }
      if (path === '/api/providers/test') {
        return Promise.resolve({ providerId: 'ollama', reachable: true, latencyMs: 12 });
      }
      return Promise.resolve([]);
    });

    await renderSettingsPage();
    await clickButtonByText('Providers');

    expect(container.textContent).toContain('Ollama');
    expect(container.textContent).toContain('1 model');

    await clickButtonByText('Test');

    expect(apiMock).toHaveBeenCalledWith('/api/providers/test', {
      method: 'POST',
      body: { providerId: 'ollama' },
    });
    expect(container.textContent).toContain('Reachable');
  });
});
