/**
 * Component: Admin Settings Core API Route Tests
 * Documentation: documentation/testing.md
 */

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPrismaMock } from '../helpers/prisma';

let authRequest: any;

const prismaMock = createPrismaMock();
const requireAuthMock = vi.hoisted(() => vi.fn());
const requireAdminMock = vi.hoisted(() => vi.fn());
const configServiceMock = vi.hoisted(() => ({
  setMany: vi.fn(),
  clearCache: vi.fn(),
}));
const audibleServiceMock = vi.hoisted(() => ({
  forceReinitialize: vi.fn(),
}));
const jobQueueMock = vi.hoisted(() => ({
  addAudibleRefreshJob: vi.fn(),
}));
const plexServiceMock = vi.hoisted(() => ({
  testConnection: vi.fn(),
}));
const pathMapperMock = vi.hoisted(() => ({
  validate: vi.fn(),
}));
const invalidateQbMock = vi.hoisted(() => vi.fn());
const invalidateSabMock = vi.hoisted(() => vi.fn());
const invalidateDownloadClientManagerMock = vi.hoisted(() => vi.fn());
const downloadClientManagerMock = vi.hoisted(() => ({
  getAllClients: vi.fn(),
  testConnection: vi.fn(),
}));

vi.mock('@/lib/db', () => ({
  prisma: prismaMock,
}));

vi.mock('@/lib/middleware/auth', () => ({
  requireAuth: requireAuthMock,
  requireAdmin: requireAdminMock,
}));

vi.mock('@/lib/services/config.service', () => ({
  getConfigService: () => configServiceMock,
}));

vi.mock('@/lib/integrations/audible.service', () => ({
  getAudibleService: () => audibleServiceMock,
}));

vi.mock('@/lib/services/job-queue.service', () => ({
  getJobQueueService: () => jobQueueMock,
}));

vi.mock('@/lib/integrations/plex.service', () => ({
  getPlexService: () => plexServiceMock,
}));

vi.mock('@/lib/utils/path-mapper', () => ({
  PathMapper: pathMapperMock,
}));

vi.mock('@/lib/integrations/qbittorrent.service', () => ({
  invalidateQBittorrentService: invalidateQbMock,
}));

vi.mock('@/lib/integrations/sabnzbd.service', () => ({
  invalidateSABnzbdService: invalidateSabMock,
}));

vi.mock('@/lib/services/download-client-manager.service', () => ({
  getDownloadClientManager: () => downloadClientManagerMock,
  invalidateDownloadClientManager: invalidateDownloadClientManagerMock,
}));

vi.mock('@/lib/services/encryption.service', () => ({
  getEncryptionService: () => ({
    encrypt: (value: string) => `enc-${value}`,
    decrypt: (value: string) => value.replace('enc-', ''),
  }),
}));

describe('Admin settings core routes', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    authRequest = { user: { id: 'admin-1', role: 'admin' }, json: vi.fn() };
    requireAuthMock.mockImplementation((_req: any, handler: any) => handler(authRequest));
    requireAdminMock.mockImplementation((_req: any, handler: any) => handler());
    // Reset download client manager mocks with default values
    downloadClientManagerMock.getAllClients.mockResolvedValue([]);
    downloadClientManagerMock.testConnection.mockResolvedValue({ success: true, message: 'Connected' });
  });

  it('returns settings payload', async () => {
    prismaMock.configuration.findMany.mockResolvedValueOnce([
      { key: 'plex_url', value: 'http://plex' },
      { key: 'plex_token', value: 'token' },
      { key: 'system.backend_mode', value: 'plex' },
    ]);
    prismaMock.user.count.mockResolvedValueOnce(0);

    const { GET } = await import('@/app/api/admin/settings/route');
    const response = await GET({} as any);
    const payload = await response.json();

    expect(payload.plex.url).toBe('http://plex');
    expect(payload.backendMode).toBe('plex');
  });

  it('updates Plex settings', async () => {
    plexServiceMock.testConnection.mockResolvedValue({ success: true, info: { machineIdentifier: 'machine' } });
    const request = {
      json: vi.fn().mockResolvedValue({
        url: 'http://plex',
        token: 'token',
        libraryId: 'lib',
        triggerScanAfterImport: true,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/plex/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(prismaMock.configuration.upsert).toHaveBeenCalled();
  });

  it('updates download client settings', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        type: 'qbittorrent',
        url: 'http://qbt',
        username: 'user',
        password: 'pass',
        remotePathMappingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/download-client/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(invalidateQbMock).toHaveBeenCalled();
  });

  it('rejects invalid download client types', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        type: 'rtorrent',
        url: 'http://rtorrent',
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/download-client/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toMatch(/Invalid client type/);
  });

  it('rejects missing qBittorrent credentials', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        type: 'qbittorrent',
        url: 'http://qbt',
        password: 'pass',
        remotePathMappingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/download-client/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toMatch(/URL, username, and password/);
  });

  it('rejects missing SABnzbd credentials', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        type: 'sabnzbd',
        url: 'http://sab',
        remotePathMappingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/download-client/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toMatch(/API key/);
  });

  it('rejects path mapping when required fields are missing', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        type: 'qbittorrent',
        url: 'http://qbt',
        username: 'user',
        password: 'pass',
        remotePathMappingEnabled: true,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/download-client/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toMatch(/Remote path and local path/);
  });

  it('rejects invalid path mapping configuration', async () => {
    pathMapperMock.validate.mockImplementationOnce(() => {
      throw new Error('bad mapping');
    });
    const request = {
      json: vi.fn().mockResolvedValue({
        type: 'qbittorrent',
        url: 'http://qbt',
        username: 'user',
        password: 'pass',
        remotePathMappingEnabled: true,
        remotePath: '/remote',
        localPath: '/local',
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/download-client/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toMatch(/bad mapping/);
  });

  it('updates paths settings', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        downloadDir: '/downloads',
        mediaDir: '/media',
        metadataTaggingEnabled: true,
        chapterMergingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/paths/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(invalidateQbMock).toHaveBeenCalled();
  });

  it('updates paths settings with custom audiobook path template', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        downloadDir: '/downloads',
        mediaDir: '/media',
        audiobookPathTemplate: '{author}/{title} - {narrator}',
        metadataTaggingEnabled: true,
        chapterMergingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/paths/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(prismaMock.configuration.upsert).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { key: 'audiobook_path_template' },
        update: { value: '{author}/{title} - {narrator}' },
      })
    );
  });

  it('rejects paths settings when directories are the same', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        downloadDir: '/same',
        mediaDir: '/same',
        metadataTaggingEnabled: true,
        chapterMergingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/paths/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toContain('must be different');
  });

  it('rejects paths settings when directories are missing', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        downloadDir: '',
        mediaDir: '/media',
        metadataTaggingEnabled: true,
        chapterMergingEnabled: false,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/paths/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(response.status).toBe(400);
    expect(payload.error).toContain('required');
  });

  it('updates Prowlarr settings', async () => {
    const request = { json: vi.fn().mockResolvedValue({ url: 'http://prowlarr', apiKey: 'key' }) };

    const { PUT } = await import('@/app/api/admin/settings/prowlarr/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
  });

  it('updates registration settings', async () => {
    const request = { json: vi.fn().mockResolvedValue({ enabled: true, requireAdminApproval: false }) };

    const { PUT } = await import('@/app/api/admin/settings/registration/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(configServiceMock.setMany).toHaveBeenCalled();
  });

  it('updates OIDC settings', async () => {
    const request = { json: vi.fn().mockResolvedValue({ enabled: true, providerName: 'OIDC', clientSecret: 'secret' }) };

    const { PUT } = await import('@/app/api/admin/settings/oidc/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(configServiceMock.setMany).toHaveBeenCalled();
  });

  it('updates ebook settings', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({ enabled: true, format: 'epub', baseUrl: 'https://annas-archive.gl' }),
    };

    const { PUT } = await import('@/app/api/admin/settings/ebook/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(configServiceMock.setMany).toHaveBeenCalled();
  });

  it('updates Audible region and triggers refresh', async () => {
    const request = { json: vi.fn().mockResolvedValue({ region: 'us' }) };

    const { PUT } = await import('@/app/api/admin/settings/audible/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(jobQueueMock.addAudibleRefreshJob).toHaveBeenCalled();
  });

  it('updates Audiobookshelf settings', async () => {
    const request = {
      json: vi.fn().mockResolvedValue({
        serverUrl: 'http://abs',
        apiToken: 'token',
        libraryId: 'lib',
        triggerScanAfterImport: true,
      }),
    };

    const { PUT } = await import('@/app/api/admin/settings/audiobookshelf/route');
    const response = await PUT(request as any);
    const payload = await response.json();

    expect(payload.success).toBe(true);
    expect(configServiceMock.setMany).toHaveBeenCalled();
  });
});


