import { app, BrowserWindow, dialog } from 'electron';
import { autoUpdater } from 'electron-updater';

import { appDisplayNameForLocale } from '../../shared/appBranding';
import {
  ipcChannels,
  type UpdaterCheckResponse,
  type UpdaterDownloadResponse,
  type UpdaterProgressEvent,
} from '../../shared/ipc/contracts';
import type { IpcRegisterHandler } from '../ipc/router';
import { addLicenseChangeListener } from '../license/licenseService';
import type { LicenseService } from '../license/licenseService';

const STARTUP_CHECK_DELAY_MS = 12_000;
/** Не дёргать сервер чаще (смена лицензии / повторные emit). */
const RE_CHECK_COOLDOWN_MS = 30_000;
/** Ручная загрузка из модалки — не зависать бесконечно на «Загрузка…». */
const MANUAL_DOWNLOAD_TIMEOUT_MS = 30 * 60 * 1000;

function withTimeout<T>(promise: Promise<T>, ms: number, code: string): Promise<T> {
  return new Promise<T>((resolve, reject) => {
    const timer = setTimeout(() => reject(new Error(code)), ms);
    promise.then(
      (v) => {
        clearTimeout(timer);
        resolve(v);
      },
      (e: unknown) => {
        clearTimeout(timer);
        reject(e instanceof Error ? e : new Error(String(e)));
      },
    );
  });
}

/**
 * На macOS Squirrel.Mac может уже получить update-downloaded к моменту quitAndInstall.
 * При autoInstallOnAppQuit=true MacUpdater не вызывает checkForUpdates повторно и зависает.
 */
function quitAndInstallForPlatform(): void {
  if (process.platform === 'darwin') {
    autoUpdater.autoInstallOnAppQuit = false;
  }
  autoUpdater.quitAndInstall(false, true);
}

function formatUpdaterError(e: unknown): string {
  const raw = e instanceof Error ? e.message : String(e);
  if (raw === 'UPDATE_DOWNLOAD_TIMEOUT') {
    return 'Превышено время ожидания загрузки обновления';
  }
  return raw;
}

let lastCheckAt = 0;
/** Ручная установка: не показывать второй диалог из `update-downloaded`. */
let suppressAutoInstallDialog = false;
/** Версия, уже скачанная Squirrel/electron-updater (в т.ч. фоном). */
let downloadedUpdateVersion: string | null = null;

type RegisterFn = IpcRegisterHandler;

function emitUpdaterProgress(ev: UpdaterProgressEvent): void {
  for (const win of BrowserWindow.getAllWindows()) {
    if (win.isDestroyed() || win.webContents.isDestroyed()) continue;
    try {
      win.webContents.send(ipcChannels.updater.progress, ev);
    } catch {
      /* окно закрылось */
    }
  }
}

function isLicensedForUpdates(licenseService: LicenseService): boolean {
  const snap = licenseService.getStatusSync();
  return snap.active;
}

function maybeCheckForUpdates(licenseService: LicenseService, ignoreCooldown: boolean): void {
  if (!app.isPackaged) return;
  if (!isLicensedForUpdates(licenseService)) return;
  const now = Date.now();
  if (!ignoreCooldown && now - lastCheckAt < RE_CHECK_COOLDOWN_MS) return;
  lastCheckAt = now;
  emitUpdaterProgress({ phase: 'checking' });
  void autoUpdater.checkForUpdates().catch(() => undefined);
}

async function runManualUpdaterCheck(licenseService: LicenseService): Promise<UpdaterCheckResponse> {
  if (!app.isPackaged) {
    return { outcome: 'not_packaged' };
  }
  if (!isLicensedForUpdates(licenseService)) {
    return { outcome: 'no_license' };
  }
  const prevAutoDownload = autoUpdater.autoDownload;
  autoUpdater.autoDownload = false;
  emitUpdaterProgress({ phase: 'checking' });
  try {
    const result = await autoUpdater.checkForUpdates();
    if (result && result.isUpdateAvailable && result.updateInfo.version) {
      emitUpdaterProgress({ phase: 'available', version: result.updateInfo.version });
      return { outcome: 'available', version: result.updateInfo.version };
    }
    emitUpdaterProgress({ phase: 'not-available', version: app.getVersion() });
    return { outcome: 'current', currentVersion: app.getVersion() };
  } catch (e) {
    const message = e instanceof Error ? e.message : String(e);
    emitUpdaterProgress({ phase: 'error', message });
    return { outcome: 'error', message };
  } finally {
    autoUpdater.autoDownload = prevAutoDownload;
  }
}

function isUpdateAlreadyDownloaded(targetVersion: string): boolean {
  return downloadedUpdateVersion !== null && downloadedUpdateVersion === targetVersion;
}

async function runManualDownloadAndRestart(targetVersion: string): Promise<UpdaterDownloadResponse> {
  if (!app.isPackaged) {
    return { ok: false, message: 'NOT_PACKAGED' };
  }
  const prevAutoInstallOnAppQuit = autoUpdater.autoInstallOnAppQuit;
  try {
    suppressAutoInstallDialog = true;
    if (process.platform === 'darwin') {
      autoUpdater.autoInstallOnAppQuit = false;
    }

    if (!isUpdateAlreadyDownloaded(targetVersion)) {
      emitUpdaterProgress({ phase: 'downloading', version: targetVersion, percent: 0 });
      await withTimeout(
        autoUpdater.downloadUpdate(),
        MANUAL_DOWNLOAD_TIMEOUT_MS,
        'UPDATE_DOWNLOAD_TIMEOUT',
      );
    }

    emitUpdaterProgress({ phase: 'installing', version: targetVersion });
    quitAndInstallForPlatform();
    return { ok: true };
  } catch (e) {
    suppressAutoInstallDialog = false;
    const message = formatUpdaterError(e);
    emitUpdaterProgress({ phase: 'error', message });
    if (process.platform === 'darwin') {
      autoUpdater.autoInstallOnAppQuit = prevAutoInstallOnAppQuit;
    }
    return { ok: false, message };
  }
}

function registerUpdaterHandlers(register: RegisterFn, licenseService: LicenseService): void {
  register(ipcChannels.updater.check, () => runManualUpdaterCheck(licenseService));
  register(ipcChannels.updater.downloadAndRestart, (req: { version: string }) =>
    runManualDownloadAndRestart(req.version),
  );
}

function wireAutoUpdaterEvents(licenseService: LicenseService): void {
  autoUpdater.on('checking-for-update', () => {
    emitUpdaterProgress({ phase: 'checking' });
  });

  autoUpdater.on('update-available', (info) => {
    emitUpdaterProgress({ phase: 'available', version: info.version });
  });

  autoUpdater.on('update-not-available', (info) => {
    emitUpdaterProgress({ phase: 'not-available', version: info.version });
  });

  autoUpdater.on('download-progress', (progress) => {
    const percent =
      typeof progress.percent === 'number' && Number.isFinite(progress.percent)
        ? Math.round(progress.percent)
        : undefined;
    const ev: UpdaterProgressEvent = { phase: 'downloading' };
    if (percent !== undefined) ev.percent = percent;
    emitUpdaterProgress(ev);
  });

  autoUpdater.on('update-downloaded', (info) => {
    downloadedUpdateVersion = info.version;
    emitUpdaterProgress({ phase: 'downloading', version: info.version, percent: 100 });
    if (suppressAutoInstallDialog) {
      suppressAutoInstallDialog = false;
      return;
    }
    void dialog
      .showMessageBox({
        type: 'info',
        title: appDisplayNameForLocale(app.getLocale()),
        message: `Доступна новая версия ${info.version}. Установить и перезапустить?`,
        buttons: ['Перезапустить сейчас', 'Позже'],
        defaultId: 0,
        cancelId: 1,
      })
      .then((r) => {
        if (r.response === 0) {
          emitUpdaterProgress({ phase: 'installing', version: info.version });
          quitAndInstallForPlatform();
        }
      });
  });

  autoUpdater.on('error', (e) => {
    const message = e instanceof Error ? e.message : String(e);
    emitUpdaterProgress({ phase: 'error', message });
  });

  addLicenseChangeListener(() => {
    maybeCheckForUpdates(licenseService, false);
  });
}

/**
 * Проверка обновлений: только упакованное приложение, только при активной лицензии.
 * Канал и URL задаются при сборке (`publish` → `app-update.yml` внутри установки).
 * Дифференциальное скачивание (HTTP Range / blockmap) по умолчанию **выключено**: за nginx у Gitea raw часто **400** на multi-Range, updater всё равно уходит в полный файл. Включить снова: **`DND_UPDATE_ENABLE_DIFFERENTIAL=1`** (имеет смысл только если на сервере починили Range).
 */
export function installAutoUpdater(licenseService: LicenseService, register: RegisterFn): void {
  registerUpdaterHandlers(register, licenseService);

  if (!app.isPackaged) return;

  const feedOverride = process.env.DND_UPDATE_FEED_URL?.trim();
  if (feedOverride) {
    const url = feedOverride.endsWith('/') ? feedOverride : `${feedOverride}/`;
    autoUpdater.setFeedURL({ provider: 'generic', url });
  }

  const enableDiff = process.env.DND_UPDATE_ENABLE_DIFFERENTIAL?.trim().toLowerCase();
  autoUpdater.disableDifferentialDownload = !(
    enableDiff === '1' ||
    enableDiff === 'true' ||
    enableDiff === 'yes'
  );

  autoUpdater.autoDownload = true;
  autoUpdater.autoInstallOnAppQuit = true;

  wireAutoUpdaterEvents(licenseService);

  setTimeout(() => {
    maybeCheckForUpdates(licenseService, true);
  }, STARTUP_CHECK_DELAY_MS);
}
