/**
 * Route-level tests for settings module — Fastify inject.
 */

import Fastify from 'fastify';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const repoMock = {
  getSettingsId: vi.fn((productId: string, userId: string) => `settings_${productId}_${userId}`),
  getByUserId: vi.fn(),
  upsert: vi.fn(),
};

const flagRepoMock = {
  getByKey: vi.fn(),
};

vi.mock('./repository.js', () => repoMock);
vi.mock('../flags/repository.js', () => flagRepoMock);
vi.mock('../../lib/request-context.js', () => ({
  getRequestProductId: () => 'lysnrai',
}));

const now = '2026-02-16T00:00:00.000Z';
const existingSettings = {
  id: 'settings_lysnrai_user_1',
  productId: 'lysnrai',
  userId: 'user_1',
  settings: { theme: 'dark', locale: 'en-US' },
  deviceOverrides: { iphone_1: { theme: 'light' } },
  createdAt: now,
  updatedAt: now,
};

function buildApp(withAuth = true) {
  return (async () => {
    const { settingsRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    if (withAuth) {
      app.addHook('onRequest', async req => {
        req.jwtPayload = { sub: 'user_1', productId: 'lysnrai' };
      });
    }
    await app.register(settingsRoutes, { prefix: '/api' });
    return app;
  })();
}

describe('settingsRoutes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

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

  it('GET /settings/kill-switch returns enabled when product is missing', async () => {
    const app = await buildApp();
    const res = await app.inject({ method: 'GET', url: '/api/settings/kill-switch' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.enabled).toBe(true);
    expect(data.disabled).toBe(false);
  });

  it('GET /settings/kill-switch returns disabled when flag is enabled', async () => {
    flagRepoMock.getByKey.mockResolvedValue({ enabled: true, description: 'Maintenance' });
    const app = await buildApp();

    const res = await app.inject({
      method: 'GET',
      url: '/api/settings/kill-switch?productId=lysnrai',
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.enabled).toBe(false);
    expect(data.disabled).toBe(true);
    expect(data.message).toBe('Maintenance');
  });

  it('GET /settings returns existing settings', async () => {
    repoMock.getByUserId.mockResolvedValue(existingSettings);
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/settings' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.settings.theme).toBe('dark');
  });

  it('GET /settings returns default doc when none exists', async () => {
    repoMock.getByUserId.mockResolvedValue(null);
    const app = await buildApp();

    const res = await app.inject({ method: 'GET', url: '/api/settings' });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.userId).toBe('user_1');
    expect(data.settings).toEqual({});
  });

  it('GET /settings returns 401 without auth', async () => {
    const app = await buildApp(false);
    const res = await app.inject({ method: 'GET', url: '/api/settings' });
    expect(res.statusCode).toBe(401);
  });

  it('PUT /settings merges global settings', async () => {
    repoMock.getByUserId.mockResolvedValue(existingSettings);
    repoMock.upsert.mockResolvedValue({
      ...existingSettings,
      settings: { ...existingSettings.settings, locale: 'fr-FR' },
    });
    const app = await buildApp();

    const res = await app.inject({
      method: 'PUT',
      url: '/api/settings',
      payload: { settings: { locale: 'fr-FR' } },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.settings.locale).toBe('fr-FR');
  });

  it('GET /settings/device/:deviceId resolves merged settings', async () => {
    repoMock.getByUserId.mockResolvedValue(existingSettings);
    const app = await buildApp();

    const res = await app.inject({
      method: 'GET',
      url: '/api/settings/device/iphone_1',
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.deviceId).toBe('iphone_1');
    expect(data.hasOverrides).toBe(true);
    expect(data.settings.theme).toBe('light');
  });

  it('PUT /settings/device/:deviceId sets overrides', async () => {
    repoMock.getByUserId.mockResolvedValue(existingSettings);
    repoMock.upsert.mockResolvedValue(existingSettings);
    const app = await buildApp();

    const res = await app.inject({
      method: 'PUT',
      url: '/api/settings/device/iphone_2',
      payload: { overrides: { locale: 'es-ES' } },
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.deviceId).toBe('iphone_2');
    expect(data.overrides.locale).toBe('es-ES');
  });

  it('DELETE /settings/device/:deviceId returns success when no existing doc', async () => {
    repoMock.getByUserId.mockResolvedValue(null);
    const app = await buildApp();

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/settings/device/iphone_2',
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.success).toBe(true);
  });

  it('DELETE /settings/device/:deviceId clears overrides when doc exists', async () => {
    repoMock.getByUserId.mockResolvedValue(existingSettings);
    repoMock.upsert.mockResolvedValue({ ...existingSettings, deviceOverrides: {} });
    const app = await buildApp();

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/settings/device/iphone_1',
    });

    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.success).toBe(true);
    expect(data.deviceId).toBe('iphone_1');
  });
});
