import { dialog, type BrowserWindow } from 'electron'
import { readFileSync, writeFileSync } from 'fs'
import { getSettings, setSettings, SECRET_KEYS } from './settings'
import { migrateSettingsKeys } from './settingsMigration'
import { listTemplates, replaceTemplates } from './templates'
import { isTemplateLike } from './core/validation'
import type { BackupExportResult, BackupImportResult, Settings, CommandTemplate } from '@shared/ipc'

interface BackupFile {
  version: 1
  settings: Settings
  templates: CommandTemplate[]
}

export async function exportBackup(win: BrowserWindow | undefined): Promise<BackupExportResult> {
  const opts = {
    defaultPath: 'aerofetch-backup.json',
    filters: [{ name: 'JSON', extensions: ['json'] }]
  }
  const res = await (win ? dialog.showSaveDialog(win, opts) : dialog.showSaveDialog(opts))
  if (res.canceled || !res.filePath) return { ok: false }
  // Strip credentials (proxy creds, API tokens) so a backup file shared or synced
  // to the cloud doesn't leak secrets in plaintext (M22). The user re-enters them
  // after import. Shares SECRET_KEYS with settings.ts so the lists can't drift.
  const settings = { ...getSettings() }
  for (const key of SECRET_KEYS) (settings as Record<string, unknown>)[key] = ''
  const payload: BackupFile = { version: 1, settings, templates: listTemplates() }
  try {
    writeFileSync(res.filePath, JSON.stringify(payload, null, 2))
    return { ok: true, path: res.filePath }
  } catch (e) {
    return { ok: false, error: (e as Error).message }
  }
}

export async function importBackup(win: BrowserWindow | undefined): Promise<BackupImportResult> {
  const openOpts = {
    properties: ['openFile' as const],
    filters: [{ name: 'JSON', extensions: ['json'] }]
  }
  const res = await (win ? dialog.showOpenDialog(win, openOpts) : dialog.showOpenDialog(openOpts))
  if (res.canceled || !res.filePaths[0]) return { ok: false }

  let parsed: unknown
  try {
    parsed = JSON.parse(readFileSync(res.filePaths[0], 'utf8'))
  } catch (e) {
    return { ok: false, error: `Could not read backup file: ${(e as Error).message}` }
  }
  if (!parsed || typeof parsed !== 'object') {
    return { ok: false, error: 'Not a valid AeroFetch backup file.' }
  }

  // setSettings/replaceTemplates validate and sanitize every field, so a
  // hand-edited or partial backup file degrades gracefully rather than
  // corrupting the store.
  const file = parsed as Partial<BackupFile>
  // Backups exported by an older AeroFetch carry pre-rename keys (CC1) — map
  // them forward so old backup files keep restoring cleanly.
  const incomingSettings =
    file.settings && typeof file.settings === 'object'
      ? (migrateSettingsKeys(
          file.settings as unknown as Record<string, unknown>
        ) as Partial<Settings>)
      : undefined
  // Drop non-object / id-less entries up front (audit T3) so both the consent
  // check below and replaceTemplates see only well-shaped rows. Without this a
  // backup with a null/garbage templates entry throws in sanitize() instead of
  // degrading gracefully as this function promises.
  const incomingTemplates = Array.isArray(file.templates)
    ? (file.templates as CommandTemplate[]).filter(isTemplateLike)
    : []

  // A custom-command template's `args` are extra yt-dlp flags that get spawned on
  // the next download. A backup that arrives with enableCustomCommands + a default
  // template carrying args would silently enable code execution the moment a
  // download starts — so require an explicit, informed confirmation before
  // honouring that, and import the templates in a *disabled* state if declined.
  const commandTemplates = incomingTemplates.filter(
    (t) => t && typeof t === 'object' && typeof t.args === 'string' && t.args.trim() !== ''
  )
  const wouldEnableCustomCommands =
    incomingSettings?.enableCustomCommands === true && commandTemplates.length > 0

  let applyCustomCommands = true
  if (wouldEnableCustomCommands) {
    const preview = commandTemplates
      .slice(0, 10)
      .map((t) => {
        const name = (t.name ?? '').trim() || 'Untitled command'
        const args = t.args.length > 100 ? `${t.args.slice(0, 100)}…` : t.args
        return `• ${name}: ${args}`
      })
      .join('\n')
    const more = commandTemplates.length > 10 ? `\n…and ${commandTemplates.length - 10} more.` : ''
    const msgOpts = {
      type: 'warning' as const,
      buttons: ['Enable custom commands', 'Import without enabling', 'Cancel'],
      defaultId: 1,
      cancelId: 2,
      title: 'Backup contains custom commands',
      message: `This backup enables ${commandTemplates.length} custom-command template${
        commandTemplates.length === 1 ? '' : 's'
      }.`,
      detail:
        'Custom commands run extra yt-dlp flags on every download. Only enable them if you trust this backup file.\n\n' +
        preview +
        more
    }
    const choice = await (win
      ? dialog.showMessageBox(win, msgOpts)
      : dialog.showMessageBox(msgOpts))
    if (choice.response === 2) return { ok: false } // Cancel — change nothing
    applyCustomCommands = choice.response === 0
  }

  if (incomingSettings) {
    // Never restore credential fields from a backup. Exports strip them (M22), so an
    // imported '' would otherwise wipe a proxy/token already configured on this
    // machine; honoring a hand-edited one would reintroduce the leak vector. Either
    // way, import leaves the user's existing secrets untouched — they re-enter as needed.
    const restored: Partial<Settings> = { ...incomingSettings }
    for (const key of SECRET_KEYS) delete restored[key]
    // When the user declined to enable custom commands (or there were none to
    // enable), force the toggle off so an imported defaultTemplateId can't auto-run.
    const safeSettings = applyCustomCommands
      ? restored
      : { ...restored, enableCustomCommands: false }
    setSettings(safeSettings)
  }
  if (incomingTemplates.length > 0 || Array.isArray(file.templates)) {
    replaceTemplates(incomingTemplates)
  }
  return { ok: true }
}
