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

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

const repoMock = {
  getDevicesByUser: vi.fn(),
  upsertDevice: vi.fn(),
  removeDevice: vi.fn(),
  getPrefs: vi.fn(),
  upsertPrefs: vi.fn(),
};

vi.mock('./repository.js', () => repoMock);

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

const baseDevice = {
  id: 'dev_user_1_device_abc',
  productId: 'lysnrai',
  userId: 'user_1',
  deviceId: 'device_abc',
  platform: 'ios',
  lastSeenAt: '2026-02-16T00:00:00Z',
  createdAt: '2026-02-16T00:00:00Z',
  updatedAt: '2026-02-16T00:00:00Z',
};

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

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

  it('POST /devices registers a device', async () => {
    repoMock.upsertDevice.mockResolvedValue(baseDevice);
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/devices',
      payload: {
        userId: 'user_1',
        deviceId: 'device_abc',
        platform: 'ios',
      },
    });
    expect(res.statusCode).toBe(200);
    expect(repoMock.upsertDevice).toHaveBeenCalled();
  });

  it('POST /devices rejects invalid platform', async () => {
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'POST',
      url: '/api/devices',
      payload: {
        userId: 'user_1',
        deviceId: 'device_abc',
        platform: 'linux',
      },
    });
    expect(res.statusCode).toBe(400);
  });

  it('GET /devices/:userId lists devices', async () => {
    repoMock.getDevicesByUser.mockResolvedValue([baseDevice]);
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({ method: 'GET', url: '/api/devices/user_1' });
    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.devices).toHaveLength(1);
  });

  it('DELETE /devices/:id removes a device', async () => {
    repoMock.removeDevice.mockResolvedValue(true);
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/devices/dev_1?userId=user_1',
    });
    expect(res.statusCode).toBe(200);
  });

  it('DELETE /devices/:id returns 400 without userId', async () => {
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({ method: 'DELETE', url: '/api/devices/dev_1' });
    expect(res.statusCode).toBe(400);
  });

  it('DELETE /devices/:id returns 404 when not found', async () => {
    repoMock.removeDevice.mockResolvedValue(false);
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'DELETE',
      url: '/api/devices/nonexistent?userId=user_1',
    });
    expect(res.statusCode).toBe(404);
  });

  it('GET /notifications/prefs/:userId returns prefs', async () => {
    repoMock.getPrefs.mockResolvedValue({
      pushEnabled: true,
      emailEnabled: false,
      categories: { marketing: false },
    });
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/notifications/prefs/user_1',
    });
    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.pushEnabled).toBe(true);
  });

  it('GET /notifications/prefs/:userId returns defaults when no prefs', async () => {
    repoMock.getPrefs.mockResolvedValue(null);
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'GET',
      url: '/api/notifications/prefs/user_1',
    });
    expect(res.statusCode).toBe(200);
    const data = JSON.parse(res.body);
    expect(data.pushEnabled).toBe(true);
    expect(data.emailEnabled).toBe(true);
  });

  it('PUT /notifications/prefs/:userId updates prefs', async () => {
    repoMock.getPrefs.mockResolvedValue(null);
    repoMock.upsertPrefs.mockResolvedValue({
      id: 'prefs_lysnrai_user_1',
      productId: 'lysnrai',
      userId: 'user_1',
      pushEnabled: false,
      emailEnabled: true,
      categories: {},
    });
    const { notificationRoutes } = await import('./routes.js');
    const app = Fastify({ logger: false });
    await app.register(notificationRoutes, { prefix: '/api' });

    const res = await app.inject({
      method: 'PUT',
      url: '/api/notifications/prefs/user_1',
      payload: { pushEnabled: false },
    });
    expect(res.statusCode).toBe(200);
    expect(repoMock.upsertPrefs).toHaveBeenCalled();
  });
});
