import electron from 'electron';
import type { BrowserWindow } from 'electron';
import { platform, release, EOL } from 'node:os';
import { mkdir } from 'node:fs/promises';
const { ipcMain, app, dialog, Notification, shell, clipboard } = electron;
import { logger } from '../logger.js';
import type { BackupService } from '../services/BackupService.js';
import type { ExportService } from '../services/ExportService.js';
import type { ImportService } from '../services/ImportService.js';
import type { SyncService } from '../services/SyncService.js';
import { GitClient } from '../services/GitClient.js';
import type { TelemetryService } from '../services/TelemetryService.js';
import type { SettingsService } from '../services/SettingsService.js';
import type { SyncTimer } from '../services/SyncTimer.js';
import { collectAutostartState } from '../services/AutostartDiagnostic.js';
import { getInboxWindow as getInboxWindowSingleton } from '../windows/inboxWindow.js';
import { refreshVisionCache } from '../services/VisionDetect.js';
import { DEFAULT_OLLAMA_ENDPOINT } from '../../shared/constants.js';

/**
 * 외부 (트레이 / second-instance / 기타 main 프로세스 호출자) 에서 inbox 창에 view 전환을
 * 요청하는 진입점. 창이 숨겨져 있으면 show + focus 후 'inbox:navigate' IPC 이벤트를
 * renderer 로 전달.
 *
 * Task 13 (v0.2.7) — 트레이 "설정..." 메뉴 wiring 은 Task 16 에서 본 함수 호출.
 */
export function navigateInbox(view: 'inbox' | 'trash' | 'settings'): void {
  const win = getInboxWindowSingleton();
  if (win && !win.isDestroyed()) {
    if (!win.isVisible()) win.show();
    win.focus();
    win.webContents.send('inbox:navigate', view);
  }
}

export interface SettingsIpcDeps {
  backup: BackupService;
  exportSvc: ExportService;
  importSvc: ImportService;
  syncSvc: SyncService;
  telemetry: TelemetryService;
  settings: SettingsService;
  getInboxWindow: () => BrowserWindow | null;
  syncTimer?: SyncTimer;
}

/**
 * v0.2.7 설정 페이지 IPC 핸들러.
 *
 * - 자동 실행 (Task 22 통일): `settings:autostart-state` (조회) / `settings:autostart-set` (변경).
 *   둘 다 `{ openAtLogin, diagnostic }` 반환 — diagnostic 은 withArgs/noArgs/execPath/registry 진단.
 *   args=['--hidden'] 명시 — 자동 실행 시 백그라운드 모드로 시작 (Quick Capture only).
 *
 * - 백업/내보내기/복원/동기화/사용 로그 (Task 10): 기존 `src/main/index.ts` 트레이 callback
 *   (runBackup, runExport, runImport, runSync, runExportTelemetry) 본문을 그대로 IPC 핸들러로
 *   복사. 트레이 callback 자체 제거는 Task 16 (Phase 3) — 본 task 에선 잔류 (의도적 중복).
 */
export function registerSettingsApi(deps?: SettingsIpcDeps): void {
  // v0.2.7 F12 deeper fix (Task 21~22) — 진단 정보 포함된 autostart 상태 조회/변경.
  // 옛 'settings:get-autostart' / 'settings:set-autostart' 채널은 본 통일에서 제거됨.
  ipcMain.handle('settings:autostart-state', async () => {
    const diag = await collectAutostartState();
    return { openAtLogin: diag.withArgs.openAtLogin, diagnostic: diag };
  });

  ipcMain.handle('settings:autostart-set', async (_e, open: boolean) => {
    app.setLoginItemSettings({ openAtLogin: open, args: ['--hidden'] });
    const diag = await collectAutostartState();
    return { openAtLogin: diag.withArgs.openAtLogin, diagnostic: diag };
  });

  // v0.2.7 정보 섹션 (Task 11) — 트레이 showAboutDialog 의 detail 형식 그대로 (clipboard 일관성).
  // 트레이 showAboutDialog 자체 제거는 Task 25 (Phase 6 cleanup) — 본 task 는 추가만.
  ipcMain.handle('settings:get-app-info', () => ({
    version: app.getVersion(),
    electron: process.versions.electron ?? '?',
    node: process.versions.node ?? '?',
    os: `${platform()} ${release()}`,
    profileDir: app.getPath('userData')
  }));

  ipcMain.handle('settings:open-profile-dir', async () => {
    await shell.openPath(app.getPath('userData'));
  });

  ipcMain.handle('settings:copy-app-info', () => {
    const v = app.getVersion();
    const detail = [
      `버전: ${v}`,
      `Electron: ${process.versions.electron ?? '?'}`,
      `Node: ${process.versions.node ?? '?'}`,
      `OS: ${platform()} ${release()}`,
      `데이터 위치: ${app.getPath('userData')}`
    ].join(EOL);
    clipboard.writeText(`Inkling ${v}${EOL}${detail}`);
  });

  if (!deps) return;
  const { backup, exportSvc, importSvc, syncSvc, telemetry, settings, getInboxWindow } = deps;

  // v0.2.9 Cut B Task 12 — settings read + AI/onboarding 토글.
  // 첫 launch 시 OnboardingWizard 분기 (App.tsx) 와 SettingsPage 의 ai_enabled 토글 통합.
  ipcMain.handle('settings:get', async () => settings.getAll());

  ipcMain.handle('settings:set-ai-enabled', async (_e, enabled: boolean) => {
    await settings.setAiEnabled(enabled);
    return { ok: true as const };
  });

  ipcMain.handle('settings:set-onboarding-completed', async (_e, completed: boolean) => {
    await settings.setOnboardingCompleted(completed);
    return { ok: true as const };
  });

  ipcMain.handle('settings:set-sync-auto-enabled', async (_e, value: boolean) => {
    await deps.settings.setAutoSyncEnabled(value);
    await deps.syncTimer?.reconfigure();
    return { ok: true as const };
  });

  ipcMain.handle('settings:set-sync-interval-min', async (_e, value: number) => {
    try {
      await deps.settings.setSyncIntervalMin(value);
      await deps.syncTimer?.reconfigure();
      return { ok: true as const };
    } catch (e) {
      return { ok: false as const, reason: (e as Error).message };
    }
  });

  ipcMain.handle('settings:run-backup', async () => {
    try {
      const r = await backup.runDaily();
      new Notification({
        title: 'Inkling',
        body: r.snapshotted
          ? `백업 완료 — ${r.removed?.length ?? 0}개 정리`
          : `오늘 백업이 이미 있습니다`,
        silent: true
      }).show();
    } catch (e) {
      logger.warn('backup.manual.failed', { reason: String(e) });
      new Notification({
        title: 'Inkling',
        body: '백업을 만들지 못했습니다.',
        silent: true
      }).show();
    }
    return { ok: true } as const;
  });

  ipcMain.handle('settings:run-export', async () => {
    const win = getInboxWindow();
    const dialogOpts: Electron.OpenDialogOptions = {
      title: '내보낼 폴더 선택',
      message: '선택한 폴더에 노트를 마크다운으로 내보냅니다. 이미지가 함께 포함됩니다. raw_text 가 평문으로 보관되니 비공개 위치를 권장합니다.',
      buttonLabel: '여기에 내보내기',
      properties: ['openDirectory', 'createDirectory']
    };
    const result = win
      ? await dialog.showOpenDialog(win, dialogOpts)
      : await dialog.showOpenDialog(dialogOpts);
    if (result.canceled || result.filePaths.length === 0) return { ok: true } as const;
    try {
      const r = await exportSvc.export(result.filePaths[0]!, { includeMedia: true });
      logger.info('export.done', {
        outDir: r.outDir,
        noteCount: r.noteCount,
        mediaCount: r.mediaCount,
        bytes: r.bytes
      });
      new Notification({
        title: 'Inkling',
        body: `내보내기 완료 — 노트 ${r.noteCount}개, 이미지 ${r.mediaCount}개`,
        silent: true
      }).show();
    } catch (e) {
      logger.warn('export.failed', { reason: String(e) });
      new Notification({
        title: 'Inkling',
        body: '내보내기를 완료하지 못했습니다.',
        silent: true
      }).show();
    }
    return { ok: true } as const;
  });

  ipcMain.handle('settings:run-import', async () => {
    const win = getInboxWindow();
    const dirOpts: Electron.OpenDialogOptions = {
      title: '복원할 백업 폴더 선택',
      message: 'F5 export 형식의 폴더를 선택하세요. notes/ 하위의 마크다운 파일이 적재됩니다.',
      buttonLabel: '여기서 복원',
      properties: ['openDirectory']
    };
    const dirResult = win
      ? await dialog.showOpenDialog(win, dirOpts)
      : await dialog.showOpenDialog(dirOpts);
    if (dirResult.canceled || dirResult.filePaths.length === 0) return { ok: true } as const;
    const sourceDir = dirResult.filePaths[0]!;
    let plan;
    try {
      plan = await importSvc.preview(sourceDir);
    } catch (e) {
      logger.warn('import.preview.failed', { reason: String(e) });
      new Notification({
        title: 'Inkling',
        body: '백업 폴더를 읽지 못했습니다.',
        silent: true
      }).show();
      return { ok: true } as const;
    }
    const detail = `총 ${plan.total}개 노트\n  · 신규 ${plan.newCount}개\n  · 동일 (스킵) ${plan.unchangedCount}개\n  · 충돌→새 id (${plan.forkedCount}개, raw_text 보존)\n\n이미지 ${plan.mediaCount}개 복사 예정.`;
    const confirmOpts: Electron.MessageBoxOptions = {
      type: 'question',
      buttons: ['복원', '취소'],
      defaultId: 0,
      cancelId: 1,
      title: 'Inkling 복원',
      message: '복원 미리보기',
      detail
    };
    const confirm = win
      ? await dialog.showMessageBox(win, confirmOpts)
      : await dialog.showMessageBox(confirmOpts);
    if (confirm.response !== 0) return { ok: true } as const;
    try {
      const r = await importSvc.run(sourceDir);
      logger.info('import.done', {
        total: r.total,
        new: r.newCount,
        unchanged: r.unchangedCount,
        forked: r.forkedCount,
        media: r.mediaCount
      });
      new Notification({
        title: 'Inkling',
        body: `복원 완료 — 신규 ${r.newCount}개, 스킵 ${r.unchangedCount}개, 충돌 ${r.forkedCount}개`,
        silent: true
      }).show();
    } catch (e) {
      logger.warn('import.run.failed', { reason: String(e) });
      new Notification({
        title: 'Inkling',
        body: '복원을 완료하지 못했습니다.',
        silent: true
      }).show();
    }
    return { ok: true } as const;
  });

  ipcMain.handle('settings:run-sync', async () => {
    try {
      const r = await syncSvc.sync();
      if (!r.ok) {
        logger.warn('sync.failed', { reason: r.reason });
        const body = r.reason === 'not_configured'
          ? `${syncSvc.getSyncDir()} 에서 git init + remote 설정이 필요합니다.`
          : '동기화를 완료하지 못했습니다.';
        new Notification({ title: 'Inkling', body, silent: true }).show();
        return { ok: true } as const;
      }
      if (r.changed) {
        logger.info('sync.done', { sha: r.localSha, pushed: r.pushed });
        new Notification({ title: 'Inkling', body: '동기화 완료', silent: true }).show();
      } else {
        new Notification({ title: 'Inkling', body: '변경 사항 없음', silent: true }).show();
      }
    } catch (e) {
      logger.warn('sync.exception', { reason: String(e) });
      new Notification({ title: 'Inkling', body: '동기화를 완료하지 못했습니다.', silent: true }).show();
    }
    return { ok: true } as const;
  });

  ipcMain.handle('settings:run-export-telemetry', async () => {
    const win = getInboxWindow();
    const dialogOpts: Electron.OpenDialogOptions = {
      title: '사용 로그를 내보낼 폴더 선택',
      message: '선택한 폴더에 events.jsonl + stats.md 가 생성됩니다. raw_text/요약/제목/태그 이름은 미포함입니다.',
      buttonLabel: '여기로 내보내기',
      properties: ['openDirectory', 'createDirectory']
    };
    const result = win
      ? await dialog.showOpenDialog(win, dialogOpts)
      : await dialog.showOpenDialog(dialogOpts);
    if (result.canceled || result.filePaths.length === 0) return { ok: true } as const;
    try {
      const r = await telemetry.exportTo(result.filePaths[0]!);
      logger.info('telemetry.export', { eventCount: r.eventCount, outDir: result.filePaths[0] });
      new Notification({
        title: 'Inkling',
        body: `사용 로그 내보내기 완료 — ${r.eventCount}개 이벤트`,
        silent: true
      }).show();
    } catch (e) {
      logger.warn('telemetry.export.failed', { reason: String(e) });
      new Notification({
        title: 'Inkling',
        body: '사용 로그 내보내기를 완료하지 못했습니다.',
        silent: true
      }).show();
    }
    return { ok: true } as const;
  });

  // v0.3.0 Cut E — sync IPC.

  // settings:configure-sync — URL 저장 + git init + remote add (없으면).
  // null URL → 저장만 (init 안 함). 빈 문자열도 null 처리.
  ipcMain.handle('settings:configure-sync', async (_e, url: string | null) => {
    const trimmed = typeof url === 'string' ? url.trim() : '';
    const finalUrl = trimmed.length === 0 ? null : trimmed;

    try {
      await deps.settings.setSyncRepoUrl(finalUrl);
    } catch (e) {
      return { ok: false as const, reason: `persist failed: ${(e as Error).message}` };
    }

    if (finalUrl === null) {
      await deps.syncTimer?.reconfigure();
      return { ok: true as const };
    }

    // git init + remote add origin
    const syncDir = deps.syncSvc.getSyncDir();
    try {
      await mkdir(syncDir, { recursive: true });
    } catch (e) {
      return { ok: false as const, reason: `mkdir failed: ${(e as Error).message}` };
    }
    const git = new GitClient(syncDir);

    if (!(await git.isRepo())) {
      const init = await git.run(['init']);
      if (init.exitCode !== 0) {
        return { ok: false as const, reason: `git init failed: ${init.stderr}` };
      }
    }
    if (!(await git.hasRemote())) {
      const add = await git.run(['remote', 'add', 'origin', finalUrl]);
      if (add.exitCode !== 0) {
        return { ok: false as const, reason: `remote add failed: ${add.stderr}` };
      }
    } else {
      // remote exists — update URL
      const set = await git.run(['remote', 'set-url', 'origin', finalUrl]);
      if (set.exitCode !== 0) {
        return { ok: false as const, reason: `remote set-url failed: ${set.stderr}` };
      }
    }
    await deps.syncTimer?.reconfigure();
    return { ok: true as const };
  });

  // settings:test-sync-connection — git ls-remote 결과
  ipcMain.handle('settings:test-sync-connection', async () => {
    const syncDir = deps.syncSvc.getSyncDir();
    const git = new GitClient(syncDir);
    if (!(await git.isRepo())) return { ok: false as const, reason: 'not_initialized' };
    const r = await git.run(['ls-remote', 'origin']);
    if (r.exitCode !== 0) return { ok: false as const, reason: r.stderr || 'connection failed' };
    return { ok: true as const };
  });

  // sync:list-conflicts — SyncService 캐시 결과
  ipcMain.handle('sync:list-conflicts', () => deps.syncSvc.listConflicts());

  // sync:resolve-conflict — local/remote 2 choice. path = git index conflict 경로.
  ipcMain.handle('sync:resolve-conflict', async (_e, path: string, choice: 'local' | 'remote') => {
    if (choice !== 'local' && choice !== 'remote') {
      return { ok: false as const, reason: 'invalid choice' };
    }
    return deps.syncSvc.resolveConflict(path, choice);
  });

  // sync:get-status — lastAt + lastResult + nextAt 계산
  ipcMain.handle('sync:get-status', async () => {
    const last = deps.syncSvc.getLastStatus();
    let nextAt: string | null = null;
    if (await deps.settings.isAutoSyncEnabled()) {
      const intervalMin = await deps.settings.getSyncIntervalMin();
      const baseMs = last.lastAt ? new Date(last.lastAt).getTime() : Date.now();
      nextAt = new Date(baseMs + intervalMin * 60 * 1000).toISOString();
    }
    return { lastAt: last.lastAt, lastResult: last.lastResult, nextAt };
  });

  // v0.3.1 Cut F — vision IPC

  ipcMain.handle('settings:get-vision-models', async () => {
    const cache = await deps.settings.getVisionCapableCache();
    const selected = await deps.settings.getVisionModel();
    return { models: cache.models, at: cache.at, selected };
  });

  ipcMain.handle('settings:set-vision-model', async (_e, value: string | null) => {
    const sanitized = typeof value === 'string' && value.trim().length > 0 ? value.trim() : null;
    await deps.settings.setVisionModel(sanitized);
    return { ok: true as const };
  });

  ipcMain.handle('settings:refresh-vision-cache', async () => {
    // Cut F final review fix — index.ts 의 resolvedEndpoint (settings → env → default)
    // 와 동일한 fallback 체인 사용. settings.ollama 미설정 + env / default 만 있는 dev
    // 환경에서도 manual "다시 감지" 가 동작하도록.
    const all = await deps.settings.getAll();
    const endpoint = all.ollama?.endpoint
                  ?? process.env.INKLING_OLLAMA_ENDPOINT
                  ?? DEFAULT_OLLAMA_ENDPOINT;
    return refreshVisionCache({ settings: deps.settings, endpoint });
  });
}
