import { spawn, execSync } from 'child_process';
import { promises as fs } from 'fs';
import { join } from 'path';
import { homedir } from 'os';
import log from 'electron-log';
import { dialog, BrowserWindow } from 'electron';
import { getEnvWithPath } from '../utils/shell-utils';
import { createOutputSender } from '../utils/ipc-output-helper';
import type { InstallResult } from '../../renderer/types';

const BACKUP_DIR = join(homedir(), '.inst-agent', 'backups');
const BACKUPS_INDEX = join(BACKUP_DIR, 'backups.json');
const TARGET_DATABASES = ['canvas_production', 'canvas_development', 'canvas_test'];
const DB_USER = 'postgres';

export interface BackupEntry {
  id: string;
  containerName: string;
  notes: string;
  createdAt: string;
  filePath: string;
  fileName: string;
  isDefault?: boolean;
}

async function ensureBackupDir(): Promise<void> {
  await fs.mkdir(BACKUP_DIR, { recursive: true });
}

async function readBackupsIndex(): Promise<BackupEntry[]> {
  try {
    const content = await fs.readFile(BACKUPS_INDEX, 'utf-8');
    return JSON.parse(content);
  } catch {
    return [];
  }
}

async function writeBackupsIndex(entries: BackupEntry[]): Promise<void> {
  await fs.writeFile(BACKUPS_INDEX, JSON.stringify(entries, null, 2), 'utf-8');
}

export async function getCanvasPostgresContainers(): Promise<string[]> {
  try {
    const result = execSync(
      'docker ps -a --filter "name=^canvas-postgres" --format "{{.Names}}"',
      { env: getEnvWithPath(), encoding: 'utf-8', timeout: 10000 }
    );
    return result.split('\n').map(s => s.trim()).filter(Boolean).sort();
  } catch (error) {
    log.error('Error getting canvas-postgres containers:', error);
    return [];
  }
}

export async function getBackups(): Promise<BackupEntry[]> {
  const entries = await readBackupsIndex();
  // Already stored most-recent-first (unshift on create), but sort defensively
  return entries.sort(
    (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
  );
}

export async function setDefaultBackup(id: string, isDefault: boolean): Promise<void> {
  const entries = await readBackupsIndex();
  for (const entry of entries) {
    entry.isDefault = isDefault && entry.id === id;
  }
  await writeBackupsIndex(entries);
}

export async function deleteBackup(id: string): Promise<void> {
  const entries = await readBackupsIndex();
  const entry = entries.find((e) => e.id === id);
  if (entry) {
    try { await fs.unlink(entry.filePath); } catch { /* file may already be gone */ }
  }
  await writeBackupsIndex(entries.filter((e) => e.id !== id));
}

function runShell(
  cmd: string,
  sendOutput: (msg: string) => void
): Promise<boolean> {
  return new Promise((resolve) => {
    const proc = spawn('/bin/sh', ['-c', cmd], { env: getEnvWithPath() });
    proc.stdout.on('data', (data) => sendOutput(data.toString()));
    proc.stderr.on('data', (data) => sendOutput(data.toString()));
    proc.on('error', (err) => { sendOutput(`Error: ${err.message}\n`); resolve(false); });
    proc.on('close', (code) => resolve(code === 0));
  });
}

export async function createDatabaseBackup(
  containerName: string,
  notes: string,
  window: BrowserWindow | null
): Promise<InstallResult> {
  const sendOutput = createOutputSender(window, 'backup-restore:backup-output');

  const now = new Date();
  const timestamp = now.toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
  const backupName = `canvas_full_backup_${timestamp}`;
  const tempDir = join(BACKUP_DIR, `temp_${timestamp}`);
  const finalZip = join(BACKUP_DIR, `${backupName}.zip`);

  try {
    await ensureBackupDir();
    await fs.mkdir(tempDir, { recursive: true });

    sendOutput('═══════════════════════════════════════════════════\n');
    sendOutput(`Canvas Database Backup\n`);
    sendOutput(`Container: ${containerName}\n`);
    sendOutput(`Started:   ${now.toLocaleString()}\n`);
    sendOutput('═══════════════════════════════════════════════════\n\n');

    // Step 1: Create SQL init file
    sendOutput('Step 1: Creating Database Initialization Script...\n');
    const initSql = [
      '-- Drop existing databases',
      'DROP DATABASE IF EXISTS canvas_production;',
      'DROP DATABASE IF EXISTS canvas_development;',
      'DROP DATABASE IF EXISTS canvas_test;',
      '',
      '-- Create canvas_production',
      "CREATE DATABASE canvas_production WITH ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8';",
      '-- Create canvas_development',
      "CREATE DATABASE canvas_development WITH ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8';",
      '-- Create canvas_test',
      "CREATE DATABASE canvas_test WITH ENCODING = 'UTF8' LC_COLLATE = 'en_US.utf8' LC_CTYPE = 'en_US.utf8';",
    ].join('\n');
    await fs.writeFile(join(tempDir, '00_init_dbs.sql'), initSql, 'utf-8');
    sendOutput('✓ Created 00_init_dbs.sql\n\n');

    // Step 2: Dump databases
    sendOutput('Step 2: Exporting Database Content...\n');
    for (const db of TARGET_DATABASES) {
      sendOutput(`Dumping ${db}...\n`);
      const dumpFile = join(tempDir, `${db}.sql`);
      const ok = await runShell(
        `docker exec -t "${containerName}" pg_dump -U ${DB_USER} --clean --if-exists "${db}" > "${dumpFile}"`,
        sendOutput
      );
      if (!ok) throw new Error(`Failed to dump database: ${db}`);
      sendOutput(`✓ ${db} dumped successfully\n`);
    }

    // Step 3: Create zip archive
    sendOutput(`\nStep 3: Creating archive ${backupName}.zip...\n`);
    const zipOk = await runShell(`zip -r -j "${finalZip}" "${tempDir}"`, sendOutput);
    if (!zipOk) throw new Error('Failed to create zip archive');
    sendOutput(`✓ Archive created: ${backupName}.zip\n`);

    await fs.rm(tempDir, { recursive: true, force: true });
    sendOutput('✓ Cleaned up temporary files\n');

    // Step 4: Update backups index
    sendOutput('\nStep 4: Updating backup index...\n');
    const entries = await readBackupsIndex();
    const newEntry: BackupEntry = {
      id: timestamp,
      containerName,
      notes,
      createdAt: now.toISOString(),
      filePath: finalZip,
      fileName: `${backupName}.zip`,
      isDefault: false
    };
    entries.unshift(newEntry);
    await writeBackupsIndex(entries);
    sendOutput('✓ Backup index updated\n');

    sendOutput('\n═══════════════════════════════════════════════════\n');
    sendOutput('Backup Complete!\n');
    sendOutput(`File: ${backupName}.zip\n`);
    sendOutput(`Location: ${BACKUP_DIR}\n`);
    sendOutput('═══════════════════════════════════════════════════\n');

    return {
      success: true,
      message: `Backup created successfully!\n\nFile: ${backupName}.zip\nLocation: ${BACKUP_DIR}`
    };
  } catch (error: any) {
    try { await fs.rm(tempDir, { recursive: true, force: true }); } catch { /* ignore */ }
    sendOutput(`\n✗ Backup failed: ${error.message}\n`);
    log.error('Error creating database backup:', error);
    return { success: false, message: `Backup failed: ${error.message}` };
  }
}

export async function restoreBackupWithSender(
  backupFilePath: string,
  containerName: string,
  sendOutput: (msg: string) => void
): Promise<InstallResult> {
  const now = new Date();
  const timestamp = now.toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
  const tempDir = join(BACKUP_DIR, `restore_temp_${timestamp}`);

  try {
    sendOutput('═══════════════════════════════════════════════════\n');
    sendOutput(`Canvas Database Restore\n`);
    sendOutput(`Container: ${containerName}\n`);
    sendOutput(`Source:    ${backupFilePath}\n`);
    sendOutput(`Started:   ${now.toLocaleString()}\n`);
    sendOutput('═══════════════════════════════════════════════════\n\n');

    // Step 1: Extract zip
    sendOutput('Step 1: Extracting backup archive...\n');
    await fs.mkdir(tempDir, { recursive: true });
    const unzipOk = await runShell(`unzip -o "${backupFilePath}" -d "${tempDir}"`, sendOutput);
    if (!unzipOk) throw new Error('Failed to extract backup archive');
    sendOutput('✓ Archive extracted\n\n');

    // Step 2: Run init SQL (drop & recreate databases)
    sendOutput('Step 2: Initializing databases (drop & recreate)...\n');
    const initFile = join(tempDir, '00_init_dbs.sql');
    const initOk = await runShell(
      `docker exec -i "${containerName}" psql -U ${DB_USER} < "${initFile}"`,
      sendOutput
    );
    if (!initOk) throw new Error('Failed to initialize databases');
    sendOutput('✓ Databases initialized\n\n');

    // Step 3: Restore each database
    sendOutput('Step 3: Restoring database content...\n');
    for (const db of TARGET_DATABASES) {
      sendOutput(`Restoring ${db}...\n`);
      const sqlFile = join(tempDir, `${db}.sql`);
      const ok = await runShell(
        `docker exec -i "${containerName}" psql -U ${DB_USER} -d "${db}" < "${sqlFile}"`,
        sendOutput
      );
      if (!ok) throw new Error(`Failed to restore database: ${db}`);
      sendOutput(`✓ ${db} restored successfully\n`);
    }

    // Cleanup
    await fs.rm(tempDir, { recursive: true, force: true });
    sendOutput('\n✓ Cleaned up temporary files\n');

    sendOutput('\n═══════════════════════════════════════════════════\n');
    sendOutput('Restore Complete!\n');
    sendOutput('All Canvas databases have been restored.\n');
    sendOutput('═══════════════════════════════════════════════════\n');

    return { success: true, message: 'Databases restored successfully!' };
  } catch (error: any) {
    try { await fs.rm(tempDir, { recursive: true, force: true }); } catch { /* ignore */ }
    sendOutput(`\n✗ Restore failed: ${error.message}\n`);
    log.error('Error restoring database backup:', error);
    return { success: false, message: `Restore failed: ${error.message}` };
  }
}

export async function restoreDatabaseBackup(
  backupFilePath: string,
  containerName: string,
  window: BrowserWindow | null
): Promise<InstallResult> {
  const sendOutput = createOutputSender(window, 'backup-restore:restore-output');
  return restoreBackupWithSender(backupFilePath, containerName, sendOutput);
}

export async function selectBackupFile(window: BrowserWindow | null): Promise<string | null> {
  const dialogOptions = {
    title: 'Select Backup File',
    filters: [{ name: 'Canvas Backup', extensions: ['zip'] }],
    properties: ['openFile'] as Array<'openFile'>
  };
  const result = window
    ? await dialog.showOpenDialog(window, dialogOptions)
    : await dialog.showOpenDialog(dialogOptions);

  if (result.canceled || result.filePaths.length === 0) return null;
  return result.filePaths[0];
}

export async function selectExportFile(window: BrowserWindow | null): Promise<string | null> {
  const dialogOptions = {
    title: 'Select Canvas Backup Export',
    filters: [{ name: 'Canvas Backup Export', extensions: ['zip'] }],
    properties: ['openFile'] as Array<'openFile'>
  };
  const result = window
    ? await dialog.showOpenDialog(window, dialogOptions)
    : await dialog.showOpenDialog(dialogOptions);

  if (result.canceled || result.filePaths.length === 0) return null;
  return result.filePaths[0];
}

export async function exportBackup(
  backupId: string,
  window: BrowserWindow | null
): Promise<InstallResult> {
  const entries = await readBackupsIndex();
  const entry = entries.find((e) => e.id === backupId);
  if (!entry) return { success: false, message: 'Backup not found' };

  try {
    await fs.access(entry.filePath);
  } catch {
    return { success: false, message: `Backup file not found: ${entry.filePath}` };
  }

  const defaultName = `canvas_export_${entry.id}.zip`;
  const saveDialogOptions = {
    title: 'Export Backup',
    defaultPath: join(homedir(), 'Desktop', defaultName),
    filters: [{ name: 'Canvas Backup Export', extensions: ['zip'] }],
  };
  const saveResult = window
    ? await dialog.showSaveDialog(window, saveDialogOptions)
    : await dialog.showSaveDialog(saveDialogOptions);

  if (saveResult.canceled || !saveResult.filePath) {
    return { success: false, message: 'Export cancelled' };
  }

  const exportPath = saveResult.filePath;
  const now = new Date();
  const timestamp = now.toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
  const tempDir = join(BACKUP_DIR, `export_temp_${timestamp}`);

  try {
    await fs.mkdir(tempDir, { recursive: true });
    await fs.copyFile(entry.filePath, join(tempDir, entry.fileName));
    await fs.writeFile(join(tempDir, 'backup-info.json'), JSON.stringify(entry, null, 2), 'utf-8');

    const infoDest = join(tempDir, 'backup-info.json');
    const backupDest = join(tempDir, entry.fileName);
    const ok = await runShell(`zip -j "${exportPath}" "${infoDest}" "${backupDest}"`, () => {});
    if (!ok) throw new Error('Failed to create export archive');

    await fs.rm(tempDir, { recursive: true, force: true });
    return { success: true, message: `Backup exported successfully!\n\nFile: ${exportPath}` };
  } catch (error: any) {
    try { await fs.rm(tempDir, { recursive: true, force: true }); } catch { /* ignore */ }
    log.error('Error exporting backup:', error);
    return { success: false, message: `Export failed: ${error.message}` };
  }
}

export async function importBackup(exportedZipPath: string): Promise<InstallResult> {
  const now = new Date();
  const timestamp = now.toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19);
  const tempDir = join(BACKUP_DIR, `import_temp_${timestamp}`);

  try {
    await ensureBackupDir();
    await fs.mkdir(tempDir, { recursive: true });

    const unzipOk = await runShell(`unzip -o "${exportedZipPath}" -d "${tempDir}"`, () => {});
    if (!unzipOk) throw new Error('Failed to extract export archive');

    // Read embedded metadata
    const infoPath = join(tempDir, 'backup-info.json');
    let entry: BackupEntry;
    try {
      const content = await fs.readFile(infoPath, 'utf-8');
      entry = JSON.parse(content);
    } catch {
      throw new Error('Invalid export file: backup-info.json not found or malformed');
    }

    // Find the backup zip inside the export
    const files = await fs.readdir(tempDir);
    const backupZipName = files.find((f) => f.endsWith('.zip'));
    if (!backupZipName) throw new Error('Invalid export file: backup zip not found inside archive');

    // Copy to backups dir, preserving the original filename from metadata
    const destFileName = entry.fileName || backupZipName;
    const destPath = join(BACKUP_DIR, destFileName);
    await fs.copyFile(join(tempDir, backupZipName), destPath);

    const updatedEntry: BackupEntry = {
      ...entry,
      filePath: destPath,
      fileName: destFileName,
      isDefault: false,
    };

    // Upsert into index
    const existingEntries = await readBackupsIndex();
    const idx = existingEntries.findIndex((e) => e.id === updatedEntry.id);
    if (idx >= 0) {
      existingEntries[idx] = updatedEntry;
    } else {
      existingEntries.unshift(updatedEntry);
    }
    await writeBackupsIndex(existingEntries);

    await fs.rm(tempDir, { recursive: true, force: true });

    const dateStr = new Date(updatedEntry.createdAt).toLocaleString();
    return {
      success: true,
      message: `Backup imported successfully!\n\nDate: ${dateStr}\nContainer: ${updatedEntry.containerName}${updatedEntry.notes ? `\nNotes: ${updatedEntry.notes}` : ''}`,
    };
  } catch (error: any) {
    try { await fs.rm(tempDir, { recursive: true, force: true }); } catch { /* ignore */ }
    log.error('Error importing backup:', error);
    return { success: false, message: `Import failed: ${error.message}` };
  }
}
