import { exec } from 'child_process';
import { promisify } from 'util';
import * as os from 'os';
import { childLogger } from '../../lib/logger.js';

const execAsync = promisify(exec);
const log = childLogger('system/repository');

export async function getSystemMetrics() {
  const timestamp = new Date().toISOString();
  const uptime = process.uptime();

  // Get CPU usage and load average
  const cpus = os.cpus();
  const loadAvg = os.loadavg();
  const cpuUsage = process.cpuUsage();
  const totalCpuTime = cpus.reduce((acc: number, cpu: any) => {
    return acc + (cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.steal);
  }, 0);

  // Get memory info
  const totalMem = os.totalmem();
  const freeMem = os.freemem();
  const usedMem = totalMem - freeMem;

  // Get disk info
  let diskInfo: any[] = [];
  try {
    const { stdout } = await execAsync('df -h / /var/lib/docker 2>/dev/null || df -h /');
    const lines = stdout.split('\n').slice(1);
    for (const line of lines) {
      const parts = line.split(/\s+/).filter(p => p);
      if (parts.length >= 6) {
        const usedPercent = parseInt(parts[4].replace('%', ''));
        const total = parseSize(parts[1]);
        const used = parseSize(parts[2]);
        const free = parseSize(parts[3]);
        diskInfo.push({
          path: parts[5],
          total,
          used,
          free,
          percentage: usedPercent,
        });
      }
    }
  } catch (error) {
    log.error({ err: error }, 'failed to get disk info');
    diskInfo = [{
      path: '/',
      total: 0,
      used: 0,
      free: 0,
      percentage: 0,
    }];
  }

  return {
    timestamp,
    uptime: `${Math.floor(uptime / 60)}m ${Math.floor(uptime % 60)}s`,
    cpu: {
      usage: Math.round((cpuUsage.user / totalCpuTime) * 100 * 100) / 100,
      cores: cpus.length,
      loadAverage: loadAvg.map((avg: number) => Math.round(avg * 100) / 100),
    },
    memory: {
      total: Math.round(totalMem / 1024 / 1024 / 1024),
      used: Math.round(usedMem / 1024 / 1024 / 1024),
      free: Math.round(freeMem / 1024 / 1024 / 1024),
      percentage: Math.round((usedMem / totalMem) * 100),
    },
    disk: diskInfo,
    platform: {
      nodeVersion: process.version,
      platform: process.platform,
      arch: process.arch,
      hostname: os.hostname(),
    },
  };
}

function parseSize(sizeStr: string): number {
  const units: Record<string, number> = { 'K': 1024, 'M': 1024 * 1024, 'G': 1024 * 1024 * 1024, 'T': 1024 * 1024 * 1024 * 1024 };
  const match = sizeStr.match(/^(\d+(?:\.\d+)?)([KMGT])?$/i);
  if (!match) return 0;
  const value = parseFloat(match[1]);
  const unit = (match[2] || '').toUpperCase();
  return Math.round(value * (units[unit] || 1));
}

export async function getDockerStats() {
  const images = { total: 0, dangling: 0, size: 0 };
  const containers = { total: 0, running: 0, stopped: 0, size: 0 };
  const volumes = { total: 0, unused: 0, size: 0 };

  try {
    // Get image stats
    const { stdout: imagesOutput } = await execAsync('docker images --format "{{.Size}}" 2>/dev/null');
    const imageSizes = imagesOutput.split('\n').filter(s => s).map(parseSize);
    images.total = imageSizes.length;
    images.size = imageSizes.reduce((a, b) => a + b, 0);

    const { stdout: danglingOutput } = await execAsync('docker images -f "dangling=true" --format "{{.ID}}" 2>/dev/null');
    images.dangling = danglingOutput.split('\n').filter(s => s).length;

    // Get container stats
    const { stdout: containersOutput } = await execAsync('docker ps -a --format "{{.State}}" 2>/dev/null');
    containers.total = containersOutput.split('\n').filter(s => s).length;
    containers.running = containersOutput.split('\n').filter(s => s.includes('running')).length;
    containers.stopped = containers.total - containers.running;

    const { stdout: containerSizeOutput } = await execAsync('docker ps -as --format "{{.Size}}" 2>/dev/null');
    const containerSizes = containerSizeOutput.split('\n').filter(s => s).map(parseSize);
    containers.size = containerSizes.reduce((a, b) => a + b, 0);

    // Get volume stats
    const { stdout: volumesOutput } = await execAsync('docker volume ls --format "{{.Name}}" 2>/dev/null');
    volumes.total = volumesOutput.split('\n').filter(s => s).length;

    const { stdout: unusedVolumesOutput } = await execAsync('docker volume ls -f "dangling=true" --format "{{.Name}}" 2>/dev/null');
    volumes.unused = unusedVolumesOutput.split('\n').filter(s => s).length;

    const { stdout: volumeSizeOutput } = await execAsync('docker system df --format "{{.Size}}" 2>/dev/null');
    const volumeSizes = volumeSizeOutput.split('\n').filter(s => s).map(parseSize);
    volumes.size = volumeSizes[0] || 0;
  } catch (error) {
    log.error({ err: error }, 'failed to get docker stats');
  }

  return {
    images,
    containers,
    volumes,
  };
}

export async function dockerCleanup(type: string, force: boolean = false): Promise<{ message: string; freedSpace: number }> {
  // Use the allow-list wrapper. Each cleanup type maps to a fixed sequence
  // of `docker <kind> prune -f` invocations whose argv is built by
  // `dockerPrune` — there is no template-literal shell command at any point.
  const { dockerPrune, execAllowed } = await import('../../lib/shell.js');
  type PrunePlan = Parameters<typeof dockerPrune>;

  let freedSpace = 0;
  let plans: PrunePlan[] = [];

  switch (type) {
    case 'images':
      plans = [['image', { all: force }]];
      break;
    case 'containers':
      plans = [['container', {}]];
      break;
    case 'volumes':
      plans = [['volume', {}]];
      break;
    case 'all':
      plans = [
        ['container', {}],
        ['image', { all: true }],
        ['volume', {}],
        ['builder', {}],
      ];
      break;
    default:
      throw new Error(`Unknown cleanup type: ${type}`);
  }

  const runPlans = async () => {
    for (const [kind, opts] of plans) {
      try {
        await dockerPrune(kind, opts);
      } catch (error) {
        log.error({ err: error, kind, opts }, 'docker cleanup command failed');
      }
    }
  };

  await runPlans();

  // Calculate freed space by running docker system df before and after.
  // `execAllowed` is the no-shell variant of execFile, so the format
  // template is passed as its own argv slot rather than expanded by sh.
  try {
    const before = await execAllowed('docker', ['system', 'df', '--format', '{{.Size}}']);
    const beforeSpace = parseSize(before.stdout.split('\n')[1] || '0');

    await runPlans();

    const after = await execAllowed('docker', ['system', 'df', '--format', '{{.Size}}']);
    const afterSpace = parseSize(after.stdout.split('\n')[1] || '0');

    freedSpace = beforeSpace - afterSpace;
  } catch (error) {
    log.error({ err: error }, 'failed to calculate freed space');
  }

  return {
    message: `Docker ${type} cleanup completed`,
    freedSpace: Math.round(freedSpace / 1024 / 1024), // Convert to MB
  };
}
