/**
 * Component: Configuration Service Tests
 * Documentation: documentation/backend/services/config.md
 */

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPrismaMock } from '../helpers/prisma';
import { DEFAULT_AUDIBLE_REGION } from '@/lib/types/audible';

const prismaMock = createPrismaMock();

const encryptionMock = vi.hoisted(() => ({
  encrypt: vi.fn((value: string) => `enc:${value}`),
  decrypt: vi.fn((value: string) => value.replace('enc:', '')),
}));

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

vi.mock('@/lib/services/encryption.service', () => ({
  getEncryptionService: () => encryptionMock,
}));

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

  it('decrypts encrypted values on get', async () => {
    prismaMock.configuration.findUnique.mockResolvedValue({
      key: 'plex.auth_token',
      value: 'enc:secret',
      encrypted: true,
    });

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const value = await service.get('plex.auth_token');

    expect(value).toBe('secret');
    expect(encryptionMock.decrypt).toHaveBeenCalledWith('enc:secret');
  });

  it('caches values for subsequent get calls', async () => {
    prismaMock.configuration.findUnique.mockResolvedValue({
      key: 'system.log_level',
      value: 'info',
      encrypted: false,
    });

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();

    const first = await service.get('system.log_level');
    const second = await service.get('system.log_level');

    expect(first).toBe('info');
    expect(second).toBe('info');
    expect(prismaMock.configuration.findUnique).toHaveBeenCalledTimes(1);
  });

  it('encrypts values when setting encrypted config', async () => {
    prismaMock.configuration.upsert.mockResolvedValue({});

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();

    await service.setMany([
      { key: 'plex.auth_token', value: 'secret', encrypted: true },
    ]);

    expect(encryptionMock.encrypt).toHaveBeenCalledWith('secret');
    expect(prismaMock.configuration.upsert).toHaveBeenCalledWith(
      expect.objectContaining({
        create: expect.objectContaining({
          value: 'enc:secret',
          encrypted: true,
        }),
      })
    );
  });

  it('returns default Audible region when not configured', async () => {
    prismaMock.configuration.findUnique.mockResolvedValue(null);

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const region = await service.getAudibleRegion();

    expect(region).toBe(DEFAULT_AUDIBLE_REGION);
  });

  it('returns decrypted values for a category', async () => {
    prismaMock.configuration.findMany.mockResolvedValue([
      {
        key: 'plex.token',
        value: 'enc:secret',
        encrypted: true,
        description: 'Plex token',
      },
    ]);

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const category = await service.getCategory('plex');

    expect(category['plex.token'].value).toBe('secret');
    expect(category['plex.token'].encrypted).toBe(true);
  });

  it('masks encrypted values when listing all config', async () => {
    prismaMock.configuration.findMany.mockResolvedValue([
      {
        key: 'plex.token',
        value: 'secret',
        encrypted: true,
        category: 'plex',
        description: 'Plex token',
      },
    ]);

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const all = await service.getAll();

    expect(all['plex.token'].value).toBe('***ENCRYPTED***');
    expect(all['plex.token'].category).toBe('plex');
  });

  it('defaults backend mode to plex when unset', async () => {
    prismaMock.configuration.findUnique.mockResolvedValue(null);

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const mode = await service.getBackendMode();

    expect(mode).toBe('plex');
  });

  it('returns true when audiobookshelf mode is enabled', async () => {
    prismaMock.configuration.findUnique.mockResolvedValue({
      key: 'system.backend_mode',
      value: 'audiobookshelf',
      encrypted: false,
    });

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const enabled = await service.isAudiobookshelfMode();

    expect(enabled).toBe(true);
  });

  it('builds Plex config from stored keys', async () => {
    prismaMock.configuration.findUnique.mockImplementation(async ({ where: { key } }) => {
      const values: Record<string, string> = {
        plex_url: 'http://plex',
        plex_token: 'token',
        plex_audiobook_library_id: 'lib-1',
        plex_machine_identifier: 'machine',
      };
      return values[key]
        ? { key, value: values[key], encrypted: false }
        : null;
    });

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();
    const plexConfig = await service.getPlexConfig();

    expect(plexConfig).toEqual({
      serverUrl: 'http://plex',
      authToken: 'token',
      libraryId: 'lib-1',
      machineIdentifier: 'machine',
    });
  });

  it('clears cached entries when requested', async () => {
    prismaMock.configuration.findUnique.mockResolvedValue({
      key: 'system.log_level',
      value: 'info',
      encrypted: false,
    });

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();

    const first = await service.get('system.log_level');
    prismaMock.configuration.findUnique.mockResolvedValue({
      key: 'system.log_level',
      value: 'debug',
      encrypted: false,
    });

    const cached = await service.get('system.log_level');
    service.clearCache('system.log_level');
    const updated = await service.get('system.log_level');

    expect(first).toBe('info');
    expect(cached).toBe('info');
    expect(updated).toBe('debug');
  });

  it('throws when setting configuration fails', async () => {
    prismaMock.configuration.upsert.mockRejectedValue(new Error('db failed'));

    const { ConfigurationService } = await import('@/lib/services/config.service');
    const service = new ConfigurationService();

    await expect(
      service.setMany([{ key: 'system.test', value: '1' }])
    ).rejects.toThrow('db failed');
  });
});


