/**
 * VM Disk Provisioner — creates a bounded virtual disk for DashCaddy.
 *
 * PLATFORM STRATEGY:
 *   Windows:  Dedicated WSL2 distro with a fixed-size VHDX.
 *             Docker runs inside WSL2, all data lives in the VHDX.
 *             Uninstall = wsl --unregister (deletes VHDX instantly).
 *
 *   macOS:    Lima VM with a fixed disk image.
 *             Docker runs inside Lima, all data lives in the disk image.
 *             Uninstall = limactl delete (removes VM + disk).
 *
 *   Linux:    Sparse ext4 loopback image mounted at /opt/dashcaddy-data.
 *             Docker --data-root pointed at the mount.
 *             Uninstall = unmount + rm image file.
 *
 * The user picks a disk size (default 20GB). DashCaddy is physically
 * unable to exceed it — the OS enforces the limit, not our code.
 */

const { exec } = require('child_process');
const { promisify } = require('util');
const fs = require('fs').promises;
const path = require('path');
const platformUtils = require('../shared/platform-utils');

const execAsync = promisify(exec);

// Presets users pick from in the wizard
const DISK_PRESETS = {
  minimal:  { sizeGB: 10,  label: 'Minimal (10GB)',  desc: 'DashCaddy only, a few small apps' },
  balanced: { sizeGB: 30,  label: 'Balanced (30GB)', desc: 'DashCaddy + media tools + containers' },
  power:    { sizeGB: 100, label: 'Power (100GB)',   desc: 'DashCaddy + heavy apps + lots of containers' },
  custom:   { sizeGB: 0,   label: 'Custom',          desc: 'Pick your own size' },
};

/**
 * Main provisioner class.
 */
class VMDiskProvisioner {
  constructor() {
    this.platform = platformUtils.detectOS();
  }

  /**
   * Provision the full sandboxed environment.
   *
   * @param {Object} opts
   * @param {number} opts.diskSizeGB — virtual disk size
   * @param {string} opts.installPath — where DashCaddy app files live (host)
   * @param {number} opts.apiPort
   * @param {Object} opts.domain — { mode, domain, tld, email }
   * @param {function} [opts.onProgress] — callback(statusMsg, pct)
   * @returns {Object} { success, dockerContext, dashboardUrl, vmInfo }
   */
  async provision(opts) {
    const { diskSizeGB = 30, onProgress = () => {} } = opts;

    onProgress('Checking prerequisites', 5);
    await this._checkPrerequisites();

    onProgress('Creating virtual disk (' + diskSizeGB + 'GB)', 15);
    const diskInfo = await this._createDisk(opts);

    onProgress('Starting sandbox environment', 40);
    const envInfo = await this._startEnvironment(diskInfo, opts);

    onProgress('Installing Docker in sandbox', 60);
    await this._ensureDocker(envInfo);

    onProgress('Deploying DashCaddy into sandbox', 75);
    const deployInfo = await this._deployDashCaddy(envInfo, opts);

    onProgress('Configuring services', 90);
    await this._configureServices(envInfo, opts);

    onProgress('Complete', 100);

    return {
      success: true,
      platform: this.platform,
      diskSizeGB,
      dockerContext: envInfo.dockerContext,
      dashboardUrl: deployInfo.dashboardUrl,
      vmInfo: {
        type: envInfo.type,
        name: envInfo.name,
        diskPath: diskInfo.path,
        diskSizeGB,
        dockerDataRoot: envInfo.dockerDataRoot,
      },
    };
  }

  /**
   * Remove the sandboxed environment completely.
   * @param {Object} vmInfo — from provision()
   * @param {Object} opts — { exportDataPath: null }
   */
  async destroy(vmInfo, opts = {}) {
    // Export data first if requested
    if (opts.exportDataPath) {
      await this._exportData(vmInfo, opts.exportDataPath);
    }

    switch (this.platform) {
      case 'windows': return this._destroyWSL2(vmInfo);
      case 'macos':   return this._destroyLima(vmInfo);
      case 'linux':   return this._destroyLoopback(vmInfo);
      default: throw new Error('Unsupported platform: ' + this.platform);
    }
  }

  // =========================================================================
  // PREREQUISITES
  // =========================================================================

  async _checkPrerequisites() {
    const checks = [];

    switch (this.platform) {
      case 'windows':
        checks.push(this._checkCommand('wsl', '--status', 'WSL2'));
        break;
      case 'macos':
        checks.push(this._checkCommand('limactl', 'version', 'Lima'));
        break;
      case 'linux':
        // Need root or sudo for loopback mount
        if (process.getuid && process.getuid() !== 0) {
          // Check if we can sudo
          try { await execAsync('sudo -n true', { timeout: 5000 }); }
          catch { throw new Error('Linux install needs root or passwordless sudo for loopback mount'); }
        }
        break;
    }

    const results = await Promise.all(checks);
    const failed = results.filter(r => !r.ok);
    if (failed.length) {
      throw new Error('Missing: ' + failed.map(f => f.name).join(', ') +
        '. Install instructions: https://dashcaddy.net/docs/installation');
    }
  }

  async _checkCommand(cmd, versionArg, friendlyName) {
    try {
      await execAsync(`${cmd} ${versionArg}`, { timeout: 10000 });
      return { ok: true, name: friendlyName };
    } catch {
      return { ok: false, name: friendlyName };
    }
  }

  // =========================================================================
  // WINDOWS: WSL2 Dedicated Distro
  // =========================================================================

  async _createDisk(opts) {
    if (this.platform === 'windows') return this._createWSL2Disk(opts);
    if (this.platform === 'macos')   return this._createLimaDisk(opts);
    return this._createLoopbackDisk(opts);
  }

  async _createWSL2Disk(opts) {
    const distroName = 'dashcaddy';
    const { diskSizeGB = 30 } = opts;
    const wslPath = opts.installPath || path.join(process.env.LOCALAPPDATA || 'C:\\DashCaddy', 'DashCaddy');
    const vhdxPath = path.join(wslPath, 'data.vhdx');

    // Check if distro already exists
    try {
      const { stdout } = await execAsync('wsl -l -q', { timeout: 10000 });
      if (stdout.includes(distroName)) {
        return { type: 'wsl2', distroName, path: vhdxPath, diskSizeGB, existed: true };
      }
    } catch {}

    // Download a minimal rootfs (Alpine for smallest footprint)
    await fs.mkdir(wslPath, { recursive: true });
    const rootfsUrl = 'https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-minirootfs-3.20.0-x86_64.tar.gz';
    const rootfsPath = path.join(wslPath, 'rootfs.tar.gz');

    await execAsync(`curl -L -o "${rootfsPath}" "${rootfsUrl}"`, { timeout: 120000 });

    // Import as a new WSL2 distro — the VHDX is created automatically
    // and capped by .wslconfig max disk size
    await execAsync(`wsl --import ${distroName} "${wslPath}" "${rootfsPath}" --version 2`, { timeout: 60000 });

    // Set disk size limit via wsl config
    const wslconfigPath = path.join(wslPath, '.wslconfig');
    await fs.writeFile(wslconfigPath, [
      `[wsl2]`,
      `vmDiskSize=${diskSizeGB}GB`,
      `memory=2GB`,
      `processors=2`,
    ].join('\n'));

    // Clean up rootfs download
    await fs.unlink(rootfsPath).catch(() => {});

    return { type: 'wsl2', distroName, path: vhdxPath, diskSizeGB, existed: false };
  }

  async _startEnvironment(diskInfo, opts) {
    if (this.platform === 'windows') return this._startWSL2(diskInfo, opts);
    if (this.platform === 'macos')   return this._startLima(diskInfo, opts);
    return this._startLoopback(diskInfo, opts);
  }

  async _startWSL2(diskInfo, opts) {
    const { distroName } = diskInfo;

    // Start the distro and install Docker inside
    const wslExec = (cmd) => execAsync(`wsl -d ${distroName} -- sh -c "${cmd}"`, { timeout: 60000 });

    // Update apk and install Docker + dependencies
    await wslExec('apk update && apk add docker docker-cli-compose openrc ca-certificates curl');
    await wslExec('rc-update add docker default && service docker start');

    // Create Docker data directory inside the VM
    await wslExec('mkdir -p /var/lib/docker /opt/dashcaddy');

    return {
      type: 'wsl2',
      name: distroName,
      dockerContext: 'dashcaddy-wsl',
      dockerDataRoot: '/var/lib/docker',
      exec: wslExec,
    };
  }

  // =========================================================================
  // macOS: Lima VM
  // =========================================================================

  async _createLimaDisk(opts) {
    const { diskSizeGB = 30 } = opts;
    const vmName = 'dashcaddy';
    const limaDir = path.join(process.env.HOME, '.lima', vmName);

    // Check if VM already exists
    try {
      await execAsync(`limactl list ${vmName}`, { timeout: 10000 });
      return { type: 'lima', vmName, path: limaDir, diskSizeGB, existed: true };
    } catch {}

    // Create Lima config with fixed disk
    const config = {
      vmType: 'qemu',
      arch: 'x86_64',
      images: [{
        location: 'https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img',
        arch: 'x86_64',
      }],
      cpus: 2,
      memory: '2GiB',
      disk: diskSizeGB + 'GiB',
      mounts: [],
      containerd: { system: false, user: false },
      provision: {
        mode: 'system',
        script: 'apt-get update && apt-get install -y docker.io docker-compose-plugin',
      },
      // Forward the API port
      portForwards: [{
        guestSocket: '/var/run/docker.sock',
        hostSocket: path.join(limaDir, 'sock', 'docker.sock'),
      }],
    };

    const configPath = path.join(limaDir, 'lima.yaml');
    await fs.mkdir(path.dirname(configPath), { recursive: true });
    await fs.writeFile(configPath, require('yaml').stringify ? require('yaml').stringify(config) : JSON.stringify(config, null, 2));

    await execAsync(`limactl start --name=${vmName} ${configPath}`, { timeout: 300000 });

    return { type: 'lima', vmName, path: limaDir, diskSizeGB, existed: false };
  }

  async _startLima(diskInfo, opts) {
    const { vmName } = diskInfo;

    // Ensure VM is running
    try { await execAsync(`limactl start ${vmName}`, { timeout: 60000 }); } catch {}

    const limaExec = (cmd) => execAsync(`limactl shell ${vmName} -- bash -c "${cmd}"`, { timeout: 60000 });

    // Ensure Docker is running
    await limaExec('service docker start || true');

    return {
      type: 'lima',
      name: vmName,
      dockerContext: 'dashcaddy-lima',
      dockerDataRoot: '/var/lib/docker',
      exec: limaExec,
    };
  }

  // =========================================================================
  // LINUX: Loopback ext4 image
  // =========================================================================

  async _createLoopbackDisk(opts) {
    const { diskSizeGB = 30 } = opts;
    const imagePath = '/opt/dashcaddy-data.raw';
    const mountPoint = '/opt/dashcaddy-data';

    // Check if already mounted
    try {
      const { stdout } = await execAsync('mountpoint -q /opt/dashcaddy-data && echo mounted', { timeout: 5000 });
      if (stdout.includes('mounted')) {
        return { type: 'loopback', imagePath, mountPoint, diskSizeGB, existed: true };
      }
    } catch {}

    // Create sparse image (only uses space as data fills — starts at ~0 bytes)
    const sudo = process.getuid && process.getuid() === 0 ? '' : 'sudo';
    await execAsync(`truncate -s ${diskSizeGB}G "${imagePath}"`, { timeout: 30000 });

    // Format as ext4
    await execAsync(`${sudo} mkfs.ext4 -F -L dashcaddy "${imagePath}"`, { timeout: 60000 });

    // Mount
    await execAsync(`${sudo} mkdir -p "${mountPoint}"`, { timeout: 5000 });
    await execAsync(`${sudo} mount -o loop "${imagePath}" "${mountPoint}"`, { timeout: 10000 });

    // Add to fstab for persistence across reboots
    const fstabEntry = `${imagePath} ${mountPoint} ext4 loop,defaults 0 0`;
    await execAsync(`grep -q '${imagePath}' /etc/fstab || echo '${fstabEntry}' | ${sudo} tee -a /etc/fstab`, { timeout: 5000 });

    // Point Docker data-root at the mounted volume
    await this._configureDockerDataRoot(mountPoint + '/docker', sudo);

    return { type: 'loopback', imagePath, mountPoint, diskSizeGB, existed: false };
  }

  async _startLoopback(diskInfo, opts) {
    const { mountPoint } = diskInfo;
    const sudo = process.getuid && process.getuid() === 0 ? '' : 'sudo';

    // Ensure mounted
    try {
      await execAsync(`mountpoint -q ${mountPoint} || ${sudo} mount -o loop ${diskInfo.imagePath} ${mountPoint}`, { timeout: 10000 });
    } catch {}

    // Restart Docker to pick up new data-root
    await execAsync(`${sudo} systemctl restart docker`, { timeout: 30000 }).catch(() => {});

    return {
      type: 'loopback',
      name: 'dashcaddy-loopback',
      dockerContext: 'default',
      dockerDataRoot: mountPoint + '/docker',
      exec: (cmd) => execAsync(cmd, { timeout: 60000 }),
    };
  }

  async _configureDockerDataRoot(dataRoot, sudo) {
    const daemonJsonPath = '/etc/docker/daemon.json';
    let daemonJson = {};
    try {
      daemonJson = JSON.parse(await fs.readFile(daemonJsonPath, 'utf8'));
    } catch {}

    daemonJson['data-root'] = dataRoot;

    await execAsync(`${sudo} mkdir -p ${dataRoot}`, { timeout: 5000 });
    await execAsync(`${sudo} bash -c 'cat > ${daemonJsonPath} << EOF\n${JSON.stringify(daemonJson, null, 2)}\nEOF'`, { timeout: 5000 });
  }

  // =========================================================================
  // DEPLOY + CONFIGURE (shared across platforms)
  // =========================================================================

  async _ensureDocker(envInfo) {
    // Docker was installed during VM creation per-platform.
    // Verify it's actually running.
    if (envInfo.exec) {
      try {
        await envInfo.exec('docker info > /dev/null 2>&1');
        return;
      } catch {
        // Try starting
        if (this.platform === 'windows') await envInfo.exec('service docker start || true');
        if (this.platform === 'macos')   await envInfo.exec('service docker start || true');
      }
    }
  }

  async _deployDashCaddy(envInfo, opts) {
    const apiPort = opts.apiPort || 3001;
    const dashboardPort = opts.domain?.mode === 'public' ? null : (opts.dashboardPort || 8080);

    // Inside the VM, download and run DashCaddy
    // The VM has Docker running — we deploy the same container image
    const deployScript = `
      mkdir -p /opt/dashcaddy && cd /opt/dashcaddy
      curl -fsSL https://get.dashcaddy.net/release/latest.tar.gz | tar xz
      cd dashcaddy-api && docker build -t dashcaddy-api .
      docker run -d --name dashcaddy-api --restart unless-stopped \\
        -p ${apiPort}:${apiPort} \\
        -v /opt/dashcaddy/data:/app/data \\
        -v /opt/dashcaddy/status:/app/status \\
        -v /var/run/docker.sock:/var/run/docker.sock \\
        -e NODE_ENV=production \\
        -e PORT=${apiPort} \\
        dashcaddy-api
    `;

    if (envInfo.exec) {
      await envInfo.exec(deployScript.replace(/\n/g, ' && '));
    }

    let url;
    if (opts.domain?.mode === 'public') {
      url = `https://${opts.domain.domain}`;
    } else if (opts.domain?.mode === 'custom-tld') {
      url = `https://dashcaddy${opts.domain.tld}`;
    } else {
      url = `http://localhost:${dashboardPort || 8080}`;
    }

    return { success: true, dashboardUrl: url };
  }

  async _configureServices(envInfo, opts) {
    // Port forwarding from host to VM
    if (this.platform === 'windows') {
      // WSL2 auto-forwards localhost ports to the host
      return;
    }
    if (this.platform === 'macos') {
      // Lima forwards are configured in the VM config
      return;
    }
    // Linux: container is directly accessible
  }

  // =========================================================================
  // DESTROY (uninstall)
  // =========================================================================

  async _destroyWSL2(vmInfo) {
    await execAsync(`wsl --unregister ${vmInfo.name || 'dashcaddy'}`, { timeout: 30000 });
    // VHDX is deleted by WSL on unregister
    return { success: true, message: 'WSL2 distro deleted — all data removed' };
  }

  async _destroyLima(vmInfo) {
    await execAsync(`limactl delete -f ${vmInfo.name || 'dashcaddy'}`, { timeout: 30000 });
    return { success: true, message: 'Lima VM deleted — all data removed' };
  }

  async _destroyLoopback(vmInfo) {
    const sudo = process.getuid && process.getuid() === 0 ? '' : 'sudo';
    await execAsync(`${sudo} umount ${vmInfo.mountPoint || '/opt/dashcaddy-data'}`, { timeout: 10000 }).catch(() => {});
    await execAsync(`rm -f ${vmInfo.imagePath || '/opt/dashcaddy-data.raw'}`, { timeout: 5000 });
    // Remove from fstab
    await execAsync(`${sudo} sed -i '\\#${vmInfo.imagePath || '/opt/dashcaddy-data.raw'}#d' /etc/fstab`, { timeout: 5000 }).catch(() => {});
    return { success: true, message: 'Virtual disk unmounted and deleted — all data removed' };
  }

  async _exportData(vmInfo, exportPath) {
    // Export DashCaddy config + service definitions before destroy
    if (vmInfo.type === 'wsl2') {
      await execAsync(`wsl -d ${vmInfo.name} -- tar czf /tmp/dc-export.tar.gz /opt/dashcaddy/data /opt/dashcaddy/status`, { timeout: 60000 });
      await execAsync(`wsl -d ${vmInfo.name} -- cat /tmp/dc-export.tar.gz > "${exportPath}"`, { timeout: 60000 });
    } else if (vmInfo.type === 'lima') {
      await execAsync(`limactl shell ${vmInfo.name} -- sudo tar czf /tmp/dc-export.tar.gz /opt/dashcaddy/data`, { timeout: 60000 });
      await execAsync(`limactl shell ${vmInfo.name} -- sudo cat /tmp/dc-export.tar.gz > "${exportPath}"`, { timeout: 60000 });
    } else if (vmInfo.type === 'loopback') {
      await execAsync(`tar czf "${exportPath}" -C ${vmInfo.mountPoint} data`, { timeout: 60000 });
    }
    return { success: true, exportPath };
  }

  // =========================================================================
  // STATUS
  // =========================================================================

  async getStatus() {
    const info = { platform: this.platform, running: false };

    try {
      switch (this.platform) {
        case 'windows': {
          const { stdout } = await execAsync('wsl -l -v', { timeout: 10000 });
          info.running = stdout.includes('dashcaddy') && stdout.includes('Running');
          break;
        }
        case 'macos': {
          const { stdout } = await execAsync('limactl list --json', { timeout: 10000 });
          const vms = JSON.parse(stdout);
          info.running = vms.some(v => v.name === 'dashcaddy' && v.status === 'Running');
          break;
        }
        case 'linux': {
          const { stdout } = await execAsync('mountpoint -q /opt/dashcaddy-data && echo yes', { timeout: 5000 });
          info.running = stdout.includes('yes');
          break;
        }
      }
    } catch {}

    return info;
  }
}

module.exports = { VMDiskProvisioner, DISK_PRESETS };
