import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { SettingsService } from '@main/services/SettingsService.js';

describe('SettingsService', () => {
  let dir: string;
  let svc: SettingsService;

  beforeEach(() => {
    dir = mkdtempSync(join(tmpdir(), 'inkling-settings-'));
    svc = new SettingsService(dir);
  });
  afterEach(() => { rmSync(dir, { recursive: true, force: true }); });

  it('load() returns empty object when file does not exist', async () => {
    const s = await svc.load();
    expect(s).toEqual({});
  });

  it('load() returns empty object on corrupted JSON (no throw)', async () => {
    writeFileSync(join(dir, 'settings.json'), '{ this is not json');
    const s = await svc.load();
    expect(s).toEqual({});
  });

  it('load() caches result — second call does not re-read file', async () => {
    await svc.setOllama({ endpoint: 'http://localhost:11434', model: 'gemma4:e4b' });
    const before = await svc.load();
    // 외부에서 파일 변경
    writeFileSync(join(dir, 'settings.json'), JSON.stringify({ ollama: { endpoint: 'http://lan:11434', model: 'gemma4:26b' } }));
    const after = await svc.load();
    // 캐시 적용 — 파일 변경 무시
    expect(after).toEqual(before);
  });

  it('setOllama() throws on non-URL endpoint', async () => {
    await expect(
      svc.setOllama({ endpoint: 'not-a-url', model: 'gemma4:e4b' })
    ).rejects.toThrow();
  });

  it('setOllama() persists to disk with valid JSON', async () => {
    await svc.setOllama({ endpoint: 'http://localhost:11435', model: 'gemma4:e4b' });
    const raw = readFileSync(join(dir, 'settings.json'), 'utf8');
    const parsed = JSON.parse(raw);
    expect(parsed.ollama.endpoint).toBe('http://localhost:11435');
    expect(parsed.ollama.model).toBe('gemma4:e4b');
  });

  it('setOllama() atomic write — tmp file does not remain', async () => {
    await svc.setOllama({ endpoint: 'http://localhost:11434', model: 'gemma4:e4b' });
    expect(existsSync(join(dir, 'settings.json.tmp'))).toBe(false);
    expect(existsSync(join(dir, 'settings.json'))).toBe(true);
  });

  describe('v0.3.0 Cut E — sync settings', () => {
    it('getSyncRepoUrl() defaults to null', async () => {
      expect(await svc.getSyncRepoUrl()).toBeNull();
    });

    it('setSyncRepoUrl() / getSyncRepoUrl() round-trip', async () => {
      await svc.setSyncRepoUrl('git@gitea.example:user/notes.git');
      expect(await svc.getSyncRepoUrl()).toBe('git@gitea.example:user/notes.git');
      // setting null clears
      await svc.setSyncRepoUrl(null);
      expect(await svc.getSyncRepoUrl()).toBeNull();
    });

    it('isAutoSyncEnabled() defaults to true', async () => {
      expect(await svc.isAutoSyncEnabled()).toBe(true);
    });

    it('setAutoSyncEnabled() persists', async () => {
      await svc.setAutoSyncEnabled(false);
      expect(await svc.isAutoSyncEnabled()).toBe(false);
      await svc.setAutoSyncEnabled(true);
      expect(await svc.isAutoSyncEnabled()).toBe(true);
    });

    it('getSyncIntervalMin() defaults to 30', async () => {
      expect(await svc.getSyncIntervalMin()).toBe(30);
    });

    it('setSyncIntervalMin() persists + rejects values < 5 / non-integer', async () => {
      await svc.setSyncIntervalMin(15);
      expect(await svc.getSyncIntervalMin()).toBe(15);
      await expect(svc.setSyncIntervalMin(3)).rejects.toThrow();
      await expect(svc.setSyncIntervalMin(10.5)).rejects.toThrow();
    });
  });

  describe('v0.3.1 Cut F — vision settings', () => {
    it('getVisionModel() defaults to null', async () => {
      expect(await svc.getVisionModel()).toBeNull();
    });

    it('setVisionModel() / getVisionModel() round-trip including null clear', async () => {
      await svc.setVisionModel('llava:13b');
      expect(await svc.getVisionModel()).toBe('llava:13b');
      await svc.setVisionModel(null);
      expect(await svc.getVisionModel()).toBeNull();
    });

    it('getVisionCapableCache() defaults to empty models + null at', async () => {
      const cache = await svc.getVisionCapableCache();
      expect(cache.models).toEqual([]);
      expect(cache.at).toBeNull();
    });

    it('setVisionCapableCache() persists models + ISO timestamp', async () => {
      const now = new Date('2026-05-09T12:00:00.000Z');
      await svc.setVisionCapableCache(['llava:13b', 'llava:7b'], now);
      const cache = await svc.getVisionCapableCache();
      expect(cache.models).toEqual(['llava:13b', 'llava:7b']);
      expect(cache.at).toBe('2026-05-09T12:00:00.000Z');
    });
  });
});
