const Docker = require('dockerode');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const config = require('./config');

const docker = new Docker({ socketPath: '/var/run/docker.sock' });

// Chemin du fichier de métriques écrit par le script host
const HOST_METRICS_FILE = '/opt/manus-deploy/logs/host_metrics.json';

/**
 * Récupérer les informations d'un conteneur Docker
 */
async function getContainerInfo(containerName) {
  try {
    const container = docker.getContainer(containerName);
    const info = await container.inspect();
    return {
      id: info.Id.substring(0, 12),
      name: info.Name.replace('/', ''),
      state: info.State.Status,
      running: info.State.Running,
      startedAt: info.State.StartedAt,
      image: info.Config.Image,
      created: info.Created,
    };
  } catch (err) {
    return null;
  }
}

/**
 * Récupérer les logs d'un conteneur
 */
async function getContainerLogs(containerName, tail = 100) {
  try {
    const container = docker.getContainer(containerName);
    const logs = await container.logs({
      stdout: true,
      stderr: true,
      tail: tail,
      timestamps: true,
    });
    return logs.toString('utf8');
  } catch (err) {
    return `Erreur lors de la récupération des logs: ${err.message}`;
  }
}

/**
 * Lister tous les conteneurs
 */
async function listContainers() {
  try {
    const containers = await docker.listContainers({ all: true });
    return containers.map((c) => ({
      id: c.Id.substring(0, 12),
      names: c.Names.map((n) => n.replace('/', '')),
      state: c.State,
      status: c.Status,
      image: c.Image,
      created: new Date(c.Created * 1000).toISOString(),
    }));
  } catch (err) {
    return [];
  }
}

/**
 * Redéployer une application (docker compose build + up)
 */
function redeployApp(appConfig) {
  return new Promise((resolve, reject) => {
    const appDir = path.join(config.appsBasePath, appConfig.directory);
    const cmd = `cd ${appDir} && docker compose build --no-cache && docker compose up -d`;

    const startTime = Date.now();
    const logLines = [];

    logLines.push(`[${new Date().toISOString()}] Début du redéploiement de ${appConfig.name}`);
    logLines.push(`[${new Date().toISOString()}] Répertoire: ${appDir}`);
    logLines.push(`[${new Date().toISOString()}] Commande: ${cmd}`);

    const process = exec(cmd, {
      maxBuffer: 1024 * 1024 * 10,
      timeout: 300000,
    });

    process.stdout.on('data', (data) => {
      const lines = data.toString().split('\n').filter(Boolean);
      lines.forEach((line) => {
        logLines.push(`[${new Date().toISOString()}] ${line}`);
      });
    });

    process.stderr.on('data', (data) => {
      const lines = data.toString().split('\n').filter(Boolean);
      lines.forEach((line) => {
        logLines.push(`[${new Date().toISOString()}] ${line}`);
      });
    });

    process.on('close', (code) => {
      const duration = ((Date.now() - startTime) / 1000).toFixed(1);
      if (code === 0) {
        logLines.push(`[${new Date().toISOString()}] Redéploiement terminé avec succès en ${duration}s`);
        resolve({ success: true, logs: logLines.join('\n'), duration });
      } else {
        logLines.push(`[${new Date().toISOString()}] Redéploiement échoué (code: ${code}) en ${duration}s`);
        resolve({ success: false, logs: logLines.join('\n'), duration, exitCode: code });
      }
    });

    process.on('error', (err) => {
      logLines.push(`[${new Date().toISOString()}] Erreur: ${err.message}`);
      resolve({ success: false, logs: logLines.join('\n'), error: err.message });
    });
  });
}

/**
 * Pull le dernier code depuis Gitea
 */
function gitPull(appConfig) {
  return new Promise((resolve, reject) => {
    const srcDir = path.join(config.appsBasePath, appConfig.directory, 'src');
    const cmd = `cd ${srcDir} && git pull origin main 2>&1 || git pull origin master 2>&1`;

    exec(cmd, { timeout: 60000 }, (error, stdout, stderr) => {
      if (error) {
        resolve({ success: false, output: stderr || error.message });
      } else {
        resolve({ success: true, output: stdout });
      }
    });
  });
}

/**
 * Démarrer un conteneur Docker
 */
async function startContainer(containerName) {
  try {
    const container = docker.getContainer(containerName);
    await container.start();
    return { success: true, message: `Conteneur ${containerName} démarré` };
  } catch (err) {
    return { success: false, message: err.message };
  }
}

/**
 * Arrêter un conteneur Docker
 */
async function stopContainer(containerName) {
  try {
    const container = docker.getContainer(containerName);
    await container.stop({ t: 10 });
    return { success: true, message: `Conteneur ${containerName} arrêté` };
  } catch (err) {
    return { success: false, message: err.message };
  }
}

/**
 * Redémarrer un conteneur Docker
 */
async function restartContainer(containerName) {
  try {
    const container = docker.getContainer(containerName);
    await container.restart({ t: 10 });
    return { success: true, message: `Conteneur ${containerName} redémarré` };
  } catch (err) {
    return { success: false, message: err.message };
  }
}

/**
 * Récupérer les métriques système du serveur (CPU, RAM, disque, réseau, uptime)
 *
 * Architecture LXC + Docker : le conteneur est isolé dans ses namespaces réseau/PID,
 * ce qui empêche la lecture directe de /proc pour certaines métriques.
 *
 * Solution : lire le fichier /opt/manus-deploy/logs/host_metrics.json
 * écrit en continu par le script metrics_collector.sh tournant sur le VPS hôte.
 * Ce fichier est accessible via le volume monté dans le conteneur.
 *
 * Fallback : calcul direct depuis /proc (moins précis pour RAM/uptime/réseau).
 */
function getServerMetrics() {
  return new Promise((resolve) => {
    // ── Tentative 1 : lire le fichier de métriques du host ─────────────────
    try {
      if (fs.existsSync(HOST_METRICS_FILE)) {
        const stat = fs.statSync(HOST_METRICS_FILE);
        const ageMs = Date.now() - stat.mtimeMs;
        // Fichier valide si moins de 60 secondes
        if (ageMs < 60000) {
          const raw = fs.readFileSync(HOST_METRICS_FILE, 'utf8');
          const metrics = JSON.parse(raw);
          // Rafraîchir le timestamp
          metrics.timestamp = new Date().toISOString();
          return resolve(metrics);
        }
      }
    } catch (e) {
      // Fichier absent ou corrompu → fallback
    }

    // ── Fallback : calcul depuis /proc (valeurs approchées) ────────────────
    const { execSync } = require('child_process');
    try {
      // CPU via /host/proc/stat (namespace CPU partagé avec le VPS)
      let cpuUsage = 0;
      try {
        const procStat = fs.existsSync('/host/proc/stat') ? '/host/proc/stat' : '/proc/stat';
        const s1 = execSync(`grep '^cpu ' ${procStat}`).toString().trim().split(/\s+/).slice(1).map(Number);
        execSync('sleep 0.3');
        const s2 = execSync(`grep '^cpu ' ${procStat}`).toString().trim().split(/\s+/).slice(1).map(Number);
        const total1 = s1.reduce((a, b) => a + b, 0);
        const total2 = s2.reduce((a, b) => a + b, 0);
        const dTotal = total2 - total1;
        const dIdle = s2[3] - s1[3];
        cpuUsage = dTotal > 0 ? Math.round((1 - dIdle / dTotal) * 100) : 0;
      } catch (e) {}

      // RAM depuis cgroup v2 du VPS (accessible via /sys/fs/cgroup monté ou /host/cgroup)
      let memTotal = 0, memUsed = 0, memFree = 0, memAvailable = 0;
      try {
        // Essayer /host/cgroup d'abord, puis /sys/fs/cgroup
        const cgroupBase = fs.existsSync('/host/cgroup/memory.max') ? '/host/cgroup' : '/sys/fs/cgroup';
        const memMaxRaw = fs.readFileSync(`${cgroupBase}/memory.max`, 'utf8').trim();
        const memCurrRaw = fs.readFileSync(`${cgroupBase}/memory.current`, 'utf8').trim();

        if (memMaxRaw !== 'max') {
          memTotal = parseInt(memMaxRaw);
          memUsed = parseInt(memCurrRaw);
          // Tenter de lire la RAM inactive (cache) depuis memory.stat
          try {
            const memStat = fs.readFileSync(`${cgroupBase}/memory.stat`, 'utf8');
            const inactiveFile = parseInt((memStat.match(/^inactive_file\s+(\d+)/m) || [0, 0])[1]);
            const activeFile = parseInt((memStat.match(/^active_file\s+(\d+)/m) || [0, 0])[1]);
            const cache = inactiveFile + activeFile;
            memAvailable = memTotal - memUsed + cache;
            memFree = memAvailable;
          } catch (e) {
            memAvailable = memTotal - memUsed;
            memFree = memAvailable;
          }
        }
      } catch (e) {
        // Fallback /proc/meminfo (valeurs du namespace du conteneur)
        try {
          const meminfo = fs.readFileSync('/proc/meminfo', 'utf8');
          const getVal = (key) => {
            const m = meminfo.match(new RegExp(`^${key}:\\s+(\\d+)`, 'm'));
            return m ? parseInt(m[1]) * 1024 : 0;
          };
          memTotal = getVal('MemTotal');
          memFree = getVal('MemFree');
          memAvailable = getVal('MemAvailable') || memFree;
          memUsed = memTotal - memAvailable;
        } catch (e2) {}
      }

      // Disque : df sur /
      let diskTotal = 0, diskUsed = 0, diskFree = 0;
      try {
        const diskRaw = execSync('df -B1 / | tail -1').toString().trim().split(/\s+/);
        diskTotal = parseInt(diskRaw[1]);
        diskUsed  = parseInt(diskRaw[2]);
        diskFree  = parseInt(diskRaw[3]);
      } catch (e) {}

      // Uptime depuis /proc/uptime du VPS (namespace partagé avec le VPS)
      let uptimeSeconds = 0;
      try {
        const uptimeFile = fs.existsSync('/host/proc/uptime') ? '/host/proc/uptime' : '/proc/uptime';
        uptimeSeconds = parseFloat(fs.readFileSync(uptimeFile, 'utf8').trim().split(' ')[0]) || 0;
      } catch (e) {}

      // Load average
      let load1 = 0, load5 = 0, load15 = 0;
      try {
        const loadFile = fs.existsSync('/host/proc/loadavg') ? '/host/proc/loadavg' : '/proc/loadavg';
        const loadRaw = fs.readFileSync(loadFile, 'utf8').trim().split(' ');
        load1  = parseFloat(loadRaw[0]) || 0;
        load5  = parseFloat(loadRaw[1]) || 0;
        load15 = parseFloat(loadRaw[2]) || 0;
      } catch (e) {}

      // Réseau : /proc/net/dev du VPS (namespace réseau partagé avec le VPS, pas le conteneur)
      let netRx = 0, netTx = 0;
      try {
        const netFile = fs.existsSync('/host/proc/net/dev') ? '/host/proc/net/dev' : '/proc/net/dev';
        const netRaw = fs.readFileSync(netFile, 'utf8');
        const line = netRaw.split('\n').find((l) => /^\s*(eth0|venet0|ens\d+|enp\d+)/.test(l));
        if (line) {
          const parts = line.trim().split(/\s+/);
          netRx = parseInt(parts[1]) || 0;
          netTx = parseInt(parts[9]) || 0;
        }
      } catch (e) {}

      const memPct = memTotal > 0 ? Math.round((memUsed / memTotal) * 100) : 0;
      const diskPct = diskTotal > 0 ? Math.round((diskUsed / diskTotal) * 100) : 0;

      resolve({
        cpu: { usage: cpuUsage },
        memory: {
          total: memTotal,
          used: memUsed,
          free: memFree,
          available: memAvailable,
          usagePercent: memPct,
        },
        disk: {
          total: diskTotal,
          used: diskUsed,
          free: diskFree,
          usagePercent: diskPct,
        },
        uptime: uptimeSeconds,
        load: { load1, load5, load15 },
        network: { rx: netRx, tx: netTx },
        timestamp: new Date().toISOString(),
        source: 'fallback',
      });
    } catch (err) {
      resolve({ error: err.message });
    }
  });
}

module.exports = {
  docker,
  getContainerInfo,
  getContainerLogs,
  listContainers,
  redeployApp,
  gitPull,
  startContainer,
  stopContainer,
  restartContainer,
  getServerMetrics,
};
