import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
import { join, dirname } from 'node:path';
import { z } from 'zod';

const OllamaSettingsSchema = z.object({
  endpoint: z.string().url(),
  model: z.string().min(1)
}).strict();

const SettingsSchema = z.object({
  ollama: OllamaSettingsSchema.optional(),
  // v0.2.9 Cut B — AI-less mode toggle. 기존 settings 파일에 없으면 isAiEnabled() 가
  // true 로 fallback (기본 enabled). zod default 는 file 이 존재 + 키 부재일 때만 적용 —
  // load() 의 file-missing 분기에선 cache={} 라 isAiEnabled() 의 fallback 이 작동.
  ai_enabled: z.boolean().optional(),
  onboarding_completed: z.boolean().optional(),
  // v0.3.0 Cut E — 양방향 git sync 설정. 모두 optional — 미구성 시 sync 비활성.
  sync_repo_url: z.string().nullable().optional(),
  sync_auto_enabled: z.boolean().optional(),
  sync_interval_min: z.number().int().min(5).optional(),
  // v0.3.1 Cut F
  vision_model: z.string().nullable().optional(),
  vision_capable_cache: z.array(z.string()).optional(),
  vision_cache_at: z.string().optional()
}).strict();

export type Settings = z.infer<typeof SettingsSchema>;
export type OllamaSettings = z.infer<typeof OllamaSettingsSchema>;

export class SettingsService {
  private filePath: string;
  private cache: Settings | null = null;

  constructor(profileDir: string) {
    this.filePath = join(profileDir, 'settings.json');
  }

  async load(): Promise<Settings> {
    if (this.cache !== null) return this.cache;
    try {
      const raw = await readFile(this.filePath, 'utf8');
      const parsed = JSON.parse(raw);
      this.cache = SettingsSchema.parse(parsed);
    } catch {
      this.cache = {};
    }
    return this.cache;
  }

  /**
   * v0.2.9 Cut B Task 12 — settings:get IPC 핸들러용 read-only accessor.
   * 첫 launch onboarding 분기에서 onboarding_completed 키 확인.
   */
  async getAll(): Promise<Settings> {
    return this.load();
  }

  async setOllama(value: OllamaSettings): Promise<void> {
    const validated = OllamaSettingsSchema.parse(value);
    const current = await this.load();
    const next: Settings = { ...current, ollama: validated };
    await this.persist(next);
  }

  /**
   * v0.2.9 Cut B — AI-less mode 의 기본값은 enabled (true). 기존 settings 파일을
   * 가진 사용자 (ai_enabled 키 부재) 도 무영향.
   */
  async isAiEnabled(): Promise<boolean> {
    const s = await this.load();
    return s.ai_enabled ?? true;
  }

  async setAiEnabled(value: boolean): Promise<void> {
    const current = await this.load();
    const next: Settings = { ...current, ai_enabled: value };
    await this.persist(next);
  }

  /** v0.2.9 Cut B — 첫 실행 onboarding completion 표지. 기본 false. */
  async isOnboardingCompleted(): Promise<boolean> {
    const s = await this.load();
    return s.onboarding_completed ?? false;
  }

  async setOnboardingCompleted(value: boolean): Promise<void> {
    const current = await this.load();
    const next: Settings = { ...current, onboarding_completed: value };
    await this.persist(next);
  }

  /**
   * v0.3.0 Cut E — sync 저장소 URL. null/빈 문자열 = sync 비활성. 본 메서드는 값만 저장,
   * git init/remote add 는 별도 호출자 (settings:configure-sync IPC) 가 담당.
   */
  async getSyncRepoUrl(): Promise<string | null> {
    const s = await this.load();
    return s.sync_repo_url ?? null;
  }

  async setSyncRepoUrl(value: string | null): Promise<void> {
    const current = await this.load();
    const next: Settings = { ...current, sync_repo_url: value };
    await this.persist(next);
  }

  /** v0.3.0 Cut E — 자동 주기 sync 활성. configured 일 때만 의미 있음. 기본 true. */
  async isAutoSyncEnabled(): Promise<boolean> {
    const s = await this.load();
    return s.sync_auto_enabled ?? true;
  }

  async setAutoSyncEnabled(value: boolean): Promise<void> {
    const current = await this.load();
    const next: Settings = { ...current, sync_auto_enabled: value };
    await this.persist(next);
  }

  /** v0.3.0 Cut E — 자동 주기 sync interval (분). 기본 30, min 5. */
  async getSyncIntervalMin(): Promise<number> {
    const s = await this.load();
    return s.sync_interval_min ?? 30;
  }

  async setSyncIntervalMin(value: number): Promise<void> {
    if (!Number.isInteger(value) || value < 5) {
      throw new Error(`sync_interval_min must be an integer >= 5 (got ${value})`);
    }
    const current = await this.load();
    const next: Settings = { ...current, sync_interval_min: value };
    await this.persist(next);
  }

  /** v0.3.1 Cut F — 선택된 vision model. null = 미선택. */
  async getVisionModel(): Promise<string | null> {
    const s = await this.load();
    return s.vision_model ?? null;
  }

  async setVisionModel(value: string | null): Promise<void> {
    const current = await this.load();
    const next: Settings = { ...current, vision_model: value };
    await this.persist(next);
  }

  /** v0.3.1 Cut F — /api/tags 조회 결과 캐시. 기본 빈 배열 + null timestamp. */
  async getVisionCapableCache(): Promise<{ models: string[]; at: string | null }> {
    const s = await this.load();
    return { models: s.vision_capable_cache ?? [], at: s.vision_cache_at ?? null };
  }

  async setVisionCapableCache(models: string[], now: Date): Promise<void> {
    const current = await this.load();
    const next: Settings = { ...current, vision_capable_cache: models, vision_cache_at: now.toISOString() };
    await this.persist(next);
  }

  private async persist(next: Settings): Promise<void> {
    await mkdir(dirname(this.filePath), { recursive: true });
    const tmpPath = this.filePath + '.tmp';
    await writeFile(tmpPath, JSON.stringify(next, null, 2), 'utf8');
    await rename(tmpPath, this.filePath);
    this.cache = next;
  }
}
