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

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

/**
 * 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,
    });
    // Parse buffer to string
    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, // 10MB
      timeout: 300000, // 5 minutes
    });

    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)
 */
function getServerMetrics() {
  return new Promise((resolve) => {
    const { execSync } = require("child_process");
    const fs = require("fs");
    try {
      // Lecture directe de /proc (monté depuis l'hôte via le namespace PID du conteneur)
      // Pas besoin de nsenter : /proc dans le conteneur reflète l'hôte

      // CPU : mesure via cgroup v2 usage_usec (méthode précise pour conteneur LXC)
      // Fallback sur /proc/stat normalisé par nproc si cgroup v2 non disponible
      let cpuUsage = 0;
      try {
        const ncpu = parseInt(execSync('nproc').toString().trim()) || 1;
        const cgroupPath = '/sys/fs/cgroup/cpu.stat';
        const readUsageUsec = () => {
          const stat = fs.readFileSync(cgroupPath, 'utf8');
          const m = stat.match(/^usage_usec\s+(\d+)/m);
          return m ? parseInt(m[1]) : null;
        };
        const u1 = readUsageUsec();
        if (u1 !== null) {
          // Méthode cgroup v2 : mesure la consommation CPU réelle du conteneur
          execSync('sleep 0.5');
          const u2 = readUsageUsec();
          // delta en microsecondes / (500ms * ncpu) = % CPU du conteneur
          const deltaUs = u2 - u1;
          cpuUsage = Math.min(100, Math.round(deltaUs / (500000 * ncpu) * 100));
        } else {
          // Fallback : /proc/stat normalisé par nproc
          const parseCpuLine = (line) => line.trim().split(/\s+/).slice(1).map(Number);
          const s1 = parseCpuLine(fs.readFileSync('/proc/stat', 'utf8').split('\n').find(l => l.startsWith('cpu ')));
          execSync('sleep 0.3');
          const s2 = parseCpuLine(fs.readFileSync('/proc/stat', 'utf8').split('\n').find(l => l.startsWith('cpu ')));
          const idle1 = s1[3], total1 = s1.reduce((a,b)=>a+b,0);
          const idle2 = s2[3], total2 = s2.reduce((a,b)=>a+b,0);
          const dIdle = idle2 - idle1, dTotal = total2 - total1;
          const rawCpu = dTotal > 0 ? (1 - dIdle / dTotal) * 100 : 0;
          // Normaliser par nproc pour éviter l'effet nœud physique sur LXC
          cpuUsage = Math.round(rawCpu / ncpu);
        }
      } catch(e) {}

      // RAM : lecture robuste multi-source
      // Priorité 1 : /host/proc/meminfo (hôte LXC monté via volume docker-compose)
      // Priorité 2 : /proc/meminfo si MemTotal <= 64 Go (lxcfs ou VPS direct)
      // Priorité 3 : Docker API MemTotal + ratio /proc/meminfo du nœud physique
      let memTotal = 0;
      let memFree = 0, memAvailable = 0, memUsed = 0, memBuffers = 0, memCached = 0;

      // Fonction de lecture de meminfo depuis un chemin donné
      const readMeminfo = (procPath) => {
        const raw = fs.readFileSync(procPath + '/meminfo', 'utf8');
        const map = {};
        raw.split('\n').forEach(line => {
          const idx = line.indexOf(':');
          if (idx > 0) {
            const key = line.substring(0, idx).trim();
            const rest = line.substring(idx + 1).trim();
            const val = parseInt(rest.split(' ')[0]);
            if (Number.isFinite(val)) map[key] = val * 1024;
          }
        });
        return map;
      };

      try {
        let memMap = null;
        let sourceLabel = '';

        // Priorité 1 : /host/proc monté depuis l'hôte LXC
        if (fs.existsSync('/host/proc/meminfo')) {
          try {
            const hostMap = readMeminfo('/host/proc');
            if (hostMap['MemTotal'] > 0 && hostMap['MemTotal'] <= 64 * 1024 * 1024 * 1024) {
              memMap = hostMap;
              sourceLabel = 'host/proc';
            }
          } catch(e) {}
        }

        // Priorité 2 : /proc direct si MemTotal <= 64 Go
        if (!memMap) {
          try {
            const procMap = readMeminfo('/proc');
            if (procMap['MemTotal'] > 0 && procMap['MemTotal'] <= 64 * 1024 * 1024 * 1024) {
              memMap = procMap;
              sourceLabel = '/proc direct';
            }
          } catch(e) {}
        }

        // Priorité 3 : Docker API MemTotal + ratio du nœud physique
        if (!memMap) {
          try {
            const dockerInfoRaw = execSync(
              'curl -s --unix-socket /var/run/docker.sock http://localhost/info',
              { timeout: 3000 }
            ).toString();
            const dockerInfo = JSON.parse(dockerInfoRaw);
            const dockerMemTotal = dockerInfo.MemTotal || 0;
            if (dockerMemTotal > 0) {
              // Lire le ratio d'utilisation depuis /proc du nœud physique
              const nodeMap = readMeminfo('/proc');
              const nodeTotal = nodeMap['MemTotal'] || 1;
              const nodeAvail = nodeMap['MemAvailable'] || 0;
              const usageRatio = (nodeTotal - nodeAvail) / nodeTotal;
              // Appliquer ce ratio à la vraie RAM du VPS
              memTotal = dockerMemTotal;
              memUsed = Math.round(memTotal * usageRatio);
              memFree = memTotal - memUsed;
              memAvailable = memFree;
              sourceLabel = 'docker-api+ratio';
            }
          } catch(e) {}
        }

        // Calculer les valeurs finales si memMap disponible
        if (memMap) {
          memTotal     = memMap['MemTotal'] || 0;
          memFree      = memMap['MemFree'] || 0;
          memAvailable = memMap['MemAvailable'] || 0;
          memBuffers   = memMap['Buffers'] || 0;
          const sReclaimable = memMap['SReclaimable'] || 0;
          const shmem        = memMap['Shmem'] || 0;
          memCached    = (memMap['Cached'] || 0) + (sReclaimable > shmem ? sReclaimable - shmem : 0);
          memUsed = memTotal - memFree - memBuffers - memCached;
          if (memUsed < 0) memUsed = memTotal - memAvailable;
        }
      } catch(e) {
        // Fallback ultime
        try {
          const m = readMeminfo('/proc');
          memTotal = m['MemTotal'] || 0;
          memFree  = m['MemFree'] || 0;
          memAvailable = m['MemAvailable'] || 0;
          memUsed = memTotal - memAvailable;
        } catch(e2) {}
      }

      // Disque : df sur la racine du conteneur (= hôte car pas de volume overlay)
      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 et load
      const uptimeRaw = fs.readFileSync('/proc/uptime', 'utf8').trim().split(' ');
      const uptimeSeconds = parseFloat(uptimeRaw[0]);
      const loadRaw = fs.readFileSync('/proc/loadavg', 'utf8').trim().split(' ');
      const load1 = parseFloat(loadRaw[0]);
      const load5 = parseFloat(loadRaw[1]);
      const load15 = parseFloat(loadRaw[2]);

      // Réseau
      let netRx = 0, netTx = 0;
      try {
        const netDev = fs.readFileSync('/proc/net/dev', 'utf8');
        const netLine = netDev.split('\n').find(l => /eth0|ens|enp/.test(l));
        if (netLine) {
          const parts = netLine.trim().split(/\s+/);
          netRx = parseInt(parts[1]) || 0;
          netTx = parseInt(parts[9]) || 0;
        }
      } catch(e) {}

      resolve({
        cpu: { usage: cpuUsage },
        memory: {
          total: memTotal,
          used: memUsed,
          free: memFree,
          available: memAvailable,
          buffers: memBuffers,
          cached: memCached,
          // usagePercent = RAM applicative réelle (hors cache disque, identique à `free`)
          usagePercent: Math.round((memUsed / memTotal) * 100)
        },
        disk: {
          total: diskTotal,
          used: diskUsed,
          free: diskFree,
          usagePercent: Math.round((diskUsed / diskTotal) * 100)
        },
        uptime: uptimeSeconds,
        load: { load1, load5, load15 },
        network: { rx: netRx, tx: netTx },
        timestamp: new Date().toISOString()
      });
    } catch (err) {
      resolve({ error: err.message });
    }
  });
}
module.exports = {
  docker,
  getContainerInfo,
  getContainerLogs,
  listContainers,
  redeployApp,
  gitPull,
  startContainer,
  stopContainer,
  restartContainer,
  getServerMetrics,
};
