import { describe, it, expect, vi } from 'vitest';
import { PreferencesService, PLATFORM_DEFAULTS, IMMUTABLE_KEYS } from './preferences.service.js';
import type { Db } from '@mosaicstack/db';

/**
 * Build a mock Drizzle DB where the select chain supports:
 *   db.select().from().where()            → resolves to `listRows`
 *   db.insert().values().onConflictDoUpdate() → resolves to []
 */
function makeMockDb(listRows: Array<{ key: string; value: unknown }> = []): Db {
  const chainWithLimit = {
    limit: vi.fn().mockResolvedValue([]),
    then: (resolve: (v: typeof listRows) => unknown) => Promise.resolve(listRows).then(resolve),
  };
  const selectFrom = {
    from: vi.fn().mockReturnThis(),
    where: vi.fn().mockReturnValue(chainWithLimit),
  };
  const deleteResult = {
    where: vi.fn().mockResolvedValue([]),
  };
  // Single-round-trip upsert chain: insert().values().onConflictDoUpdate()
  const insertResult = {
    values: vi.fn().mockReturnThis(),
    onConflictDoUpdate: vi.fn().mockResolvedValue([]),
  };

  return {
    select: vi.fn().mockReturnValue(selectFrom),
    delete: vi.fn().mockReturnValue(deleteResult),
    insert: vi.fn().mockReturnValue(insertResult),
  } as unknown as Db;
}

describe('PreferencesService', () => {
  describe('getEffective', () => {
    it('returns platform defaults when user has no overrides', async () => {
      const db = makeMockDb([]);
      const service = new PreferencesService(db);
      const result = await service.getEffective('user-1');

      expect(result['agent.thinkingLevel']).toBe('auto');
      expect(result['agent.streamingEnabled']).toBe(true);
      expect(result['session.autoCompactEnabled']).toBe(true);
      expect(result['session.autoCompactThreshold']).toBe(0.8);
    });

    it('applies user overrides for mutable keys', async () => {
      const db = makeMockDb([
        { key: 'agent.thinkingLevel', value: 'high' },
        { key: 'response.language', value: 'es' },
      ]);

      const service = new PreferencesService(db);
      const result = await service.getEffective('user-1');

      expect(result['agent.thinkingLevel']).toBe('high');
      expect(result['response.language']).toBe('es');
    });

    it('ignores user overrides for immutable keys — enforcement always wins', async () => {
      const db = makeMockDb([
        { key: 'limits.maxThinkingLevel', value: 'high' },
        { key: 'limits.rateLimit', value: 9999 },
      ]);

      const service = new PreferencesService(db);
      const result = await service.getEffective('user-1');

      // Should still be null (platform default), not the user-supplied values
      expect(result['limits.maxThinkingLevel']).toBeNull();
      expect(result['limits.rateLimit']).toBeNull();
    });
  });

  describe('set', () => {
    it('returns error when attempting to override an immutable key', async () => {
      const db = makeMockDb();
      const service = new PreferencesService(db);

      const result = await service.set('user-1', 'limits.maxThinkingLevel', 'high');
      expect(result.success).toBe(false);
      expect(result.message).toContain('platform enforcement');
    });

    it('returns error when attempting to override limits.rateLimit', async () => {
      const db = makeMockDb();
      const service = new PreferencesService(db);

      const result = await service.set('user-1', 'limits.rateLimit', 100);
      expect(result.success).toBe(false);
      expect(result.message).toContain('platform enforcement');
    });

    it('upserts a mutable preference and returns success', async () => {
      // Single-round-trip INSERT … ON CONFLICT DO UPDATE path.
      const db = makeMockDb([]);
      const service = new PreferencesService(db);
      const result = await service.set('user-1', 'agent.thinkingLevel', 'high');
      expect(result.success).toBe(true);
      expect(result.message).toContain('"agent.thinkingLevel"');
    });
  });

  describe('reset', () => {
    it('returns error when attempting to reset an immutable key', async () => {
      const db = makeMockDb();
      const service = new PreferencesService(db);

      const result = await service.reset('user-1', 'limits.rateLimit');
      expect(result.success).toBe(false);
      expect(result.message).toContain('platform enforcement');
    });

    it('deletes user override and returns default value in message', async () => {
      const db = makeMockDb();
      const service = new PreferencesService(db);
      const result = await service.reset('user-1', 'agent.thinkingLevel');

      expect(result.success).toBe(true);
      expect(result.message).toContain('"auto"'); // platform default for agent.thinkingLevel
    });
  });

  describe('IMMUTABLE_KEYS', () => {
    it('contains only the enforcement keys', () => {
      expect(IMMUTABLE_KEYS.has('limits.maxThinkingLevel')).toBe(true);
      expect(IMMUTABLE_KEYS.has('limits.rateLimit')).toBe(true);
      expect(IMMUTABLE_KEYS.has('agent.thinkingLevel')).toBe(false);
    });
  });

  describe('PLATFORM_DEFAULTS', () => {
    it('has all expected keys', () => {
      const expectedKeys = [
        'agent.defaultModel',
        'agent.thinkingLevel',
        'agent.streamingEnabled',
        'response.language',
        'response.codeAnnotations',
        'safety.confirmDestructiveTools',
        'session.autoCompactThreshold',
        'session.autoCompactEnabled',
        'limits.maxThinkingLevel',
        'limits.rateLimit',
      ];
      for (const key of expectedKeys) {
        expect(Object.prototype.hasOwnProperty.call(PLATFORM_DEFAULTS, key)).toBe(true);
      }
    });
  });
});
