import { Router } from 'express';
import { config } from '../config.js';
import { resticSnapshotFileSize, spawnCommand, streamResticDumpToDd } from '../executor.js';
import { appendJobLog, createJob, finishJob, setJobProgress, setJobRunning, setJobStep } from '../jobs.js';
import { validateSnapshotForVm, validateVmExists } from '../validators.js';

export const restoreRouter = Router();

restoreRouter.post('/:vmName', async (req, res, next) => {
  try {
    const vmName = req.params.vmName;
    const { snapshotId, confirmVmName } = req.body || {};
    if (confirmVmName !== vmName) {
      const error = new Error('Restore confirmation does not match VM name.');
      error.status = 400;
      throw error;
    }

    const instance = await validateVmExists(vmName);
    if (instance.type === 'container') {
      const error = new Error('Container restore is not implemented yet. Container backups can be created, but restore needs a safe zfs receive workflow.');
      error.status = 501;
      throw error;
    }
    const snapshot = await validateSnapshotForVm(vmName, snapshotId);
    const job = createJob('restore', vmName);
    runRestoreJob(job, snapshot.id).catch(() => {});
    res.status(202).json({ jobId: job.id, message: 'Restore job started.' });
  } catch (error) {
    next(error);
  }
});

async function runRestoreJob(job, snapshotId) {
  const zvol = `${config.zfsPoolName}/virtual-machines/${job.vmName}.block`;
  const device = `/dev/zvol/${zvol}`;
  const rollbackSnapshot = `${zvol}@pre-restore-${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}`;
  let volmodeDev = false;
  let diskWriteOk = false;
  let rollbackSnapshotCreated = false;

  try {
    setJobRunning(job, 'Stopping VM');
    setJobProgress(job, { percent: 3 });
    await spawnCommand('incus', ['stop', job.vmName, '--force'], {
      ignoreExitCode: true,
      log: (line) => appendJobLog(job, line),
    });

    setJobStep(job, 'Creating pre-restore ZFS snapshot');
    setJobProgress(job, { percent: 6 });
    await spawnCommand('zfs', ['snapshot', rollbackSnapshot], { log: (line) => appendJobLog(job, line) });
    rollbackSnapshotCreated = true;
    appendJobLog(job, `Created rollback snapshot ${rollbackSnapshot}`);

    setJobStep(job, 'Setting ZFS volume to device mode');
    setJobProgress(job, { percent: 10 });
    await spawnCommand('zfs', ['set', 'volmode=dev', zvol], { log: (line) => appendJobLog(job, line) });
    volmodeDev = true;

    setJobStep(job, 'Settling device nodes');
    setJobProgress(job, { percent: 14 });
    await spawnCommand('udevadm', ['trigger'], { log: (line) => appendJobLog(job, line) });
    await spawnCommand('udevadm', ['settle'], { log: (line) => appendJobLog(job, line) });
    await new Promise((resolve) => setTimeout(resolve, 2000));

    setJobStep(job, 'Checking restore size');
    const totalBytes = await zfsVolumeSize(zvol);
    const resticBytes = await resticSnapshotFileSize(snapshotId, `${job.vmName}.raw`);
    if (totalBytes && resticBytes && totalBytes !== resticBytes) {
      throw new Error(`Restore size mismatch: Restic file is ${formatBytes(resticBytes)}, target volume is ${formatBytes(totalBytes)}.`);
    }

    setJobStep(job, 'Writing Restic snapshot to block device');
    await streamResticDumpToDd(snapshotId, `${job.vmName}.raw`, device, (line) => appendJobLog(job, line), {
      totalBytes,
      onProgress: ({ currentBytes, totalBytes: bytesTotal, percent }) => {
        setJobProgress(job, {
          currentBytes,
          totalBytes: bytesTotal,
          percent: 15 + percent * 0.78,
          detail: `Writing ${formatBytes(currentBytes)} of ${formatBytes(bytesTotal)}`,
        });
      },
    });
    diskWriteOk = true;

    setJobStep(job, 'Restoring ZFS volume mode');
    setJobProgress(job, { percent: 94 });
    await spawnCommand('zfs', ['set', 'volmode=none', zvol], { log: (line) => appendJobLog(job, line) });
    volmodeDev = false;

    setJobStep(job, 'Starting VM');
    setJobProgress(job, { percent: 97 });
    await spawnCommand('incus', ['start', job.vmName], { log: (line) => appendJobLog(job, line) });

    appendJobLog(job, `Pre-restore rollback snapshot kept: ${rollbackSnapshot}`);
    finishJob(job, 'success');
  } catch (error) {
    appendJobLog(job, error.message);
    if (volmodeDev) {
      setJobStep(job, 'Restoring ZFS volume mode after failure');
      await spawnCommand('zfs', ['set', 'volmode=none', zvol], {
        ignoreExitCode: true,
        log: (line) => appendJobLog(job, line),
      }).catch((cleanupError) => appendJobLog(job, cleanupError.message));
    }
    if (rollbackSnapshotCreated && !diskWriteOk) {
      setJobStep(job, 'Rolling back failed restore');
      await spawnCommand('zfs', ['rollback', '-r', rollbackSnapshot], {
        ignoreExitCode: true,
        log: (line) => appendJobLog(job, line),
      }).catch((cleanupError) => appendJobLog(job, cleanupError.message));
      appendJobLog(job, `Rolled back to ${rollbackSnapshot}`);
    }
    appendJobLog(job, 'VM was not restarted because restore did not complete successfully.');
    finishJob(job, 'failed', error);
  }
}

async function zfsVolumeSize(zvol) {
  const result = await spawnCommand('zfs', ['get', '-Hp', '-o', 'value', 'volsize', zvol]);
  const size = Number(result.stdout.trim());
  return Number.isFinite(size) && size > 0 ? size : null;
}

function formatBytes(value) {
  if (!value) return '-';
  const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
  let size = Number(value);
  let unitIndex = 0;
  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex += 1;
  }
  return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
}
