/**
 * Docker Maintenance Module
 * Scheduled cleanup to prevent Docker disk bloat:
 * - Prunes dangling images
 * - Prunes stopped non-managed containers
 * - Clears build cache
 * - Monitors disk usage and warns when thresholds exceeded
 */

const Docker = require('dockerode');
const EventEmitter = require('events');
const { DOCKER } = require('../utilities/constants');

const docker = new Docker();

class DockerMaintenance extends EventEmitter {
  constructor() {
    super();
    this.interval = null;
    this.running = false;
    this.lastRun = null;
    this.lastResult = null;
  }

  start() {
    if (this.running) return;
    this.running = true;

    // Run first maintenance 5 minutes after startup (let everything settle)
    setTimeout(() => {
      if (!this.running) return;
      this.runMaintenance().catch(() => {});
    }, 5 * 60 * 1000);

    // Then run on the configured interval (default 24h)
    this.interval = setInterval(() => {
      this.runMaintenance().catch(() => {});
    }, DOCKER.MAINTENANCE.INTERVAL);
  }

  stop() {
    if (!this.running) return;
    this.running = false;
    if (this.interval) {
      clearInterval(this.interval);
      this.interval = null;
    }
  }

  async runMaintenance() {
    const startTime = Date.now();
    const result = {
      timestamp: new Date().toISOString(),
      pruned: { images: 0, containers: 0, buildCache: 0 },
      spaceReclaimed: { images: 0, containers: 0, buildCache: 0, total: 0 },
      diskUsage: null,
      warnings: [],
      containersWithoutLogLimits: []
    };

    try {
      // 1. Prune dangling images
      try {
        const imgResult = await docker.pruneImages({ filters: { dangling: { true: true } } });
        result.pruned.images = (imgResult.ImagesDeleted || []).length;
        result.spaceReclaimed.images = imgResult.SpaceReclaimed || 0;
      } catch (e) {
        result.warnings.push(`Image prune failed: ${e.message}`);
      }

      // 2. Prune stopped containers (only non-managed ones)
      try {
        const stopped = await docker.listContainers({
          all: true,
          filters: { status: ['exited', 'dead'] }
        });
        for (const c of stopped) {
          // Skip DashCaddy-managed containers — user may want to restart them
          if (c.Labels?.['sami.managed'] === 'true') continue;
          // Skip containers stopped less than 24h ago
          const stoppedAge = Date.now() / 1000 - c.Created;
          if (stoppedAge < 86400) continue;
          try {
            const container = docker.getContainer(c.Id);
            await container.remove({ force: true });
            result.pruned.containers++;
          } catch (e) {
            // Container may have been removed between list and remove
          }
        }
      } catch (e) {
        result.warnings.push(`Container prune failed: ${e.message}`);
      }

      // 3. Prune build cache
      try {
        const cacheResult = await docker.pruneBuilder();
        result.spaceReclaimed.buildCache = cacheResult.SpaceReclaimed || 0;
        result.pruned.buildCache = (cacheResult.CachesDeleted || []).length;
      } catch (e) {
        // Build cache prune may not be available on all Docker versions
        result.warnings.push(`Build cache prune failed: ${e.message}`);
      }

      // 4. Get disk usage
      try {
        const df = await docker.df();
        result.diskUsage = {
          images: {
            count: (df.Images || []).length,
            sizeBytes: (df.Images || []).reduce((sum, i) => sum + (i.Size || 0), 0)
          },
          containers: {
            count: (df.Containers || []).length,
            sizeBytes: (df.Containers || []).reduce((sum, c) => sum + (c.SizeRw || 0), 0)
          },
          volumes: {
            count: (df.Volumes?.Volumes || []).length,
            sizeBytes: (df.Volumes?.Volumes || []).reduce((sum, v) => sum + (v.UsageData?.Size || 0), 0)
          },
          buildCache: {
            count: (df.BuildCache || []).length,
            sizeBytes: (df.BuildCache || []).reduce((sum, b) => sum + (b.Size || 0), 0)
          }
        };
        result.diskUsage.totalBytes =
          result.diskUsage.images.sizeBytes +
          result.diskUsage.containers.sizeBytes +
          result.diskUsage.volumes.sizeBytes +
          result.diskUsage.buildCache.sizeBytes;
        result.diskUsage.totalGB = +(result.diskUsage.totalBytes / (1024 ** 3)).toFixed(2);

        if (result.diskUsage.totalGB > DOCKER.MAINTENANCE.DISK_WARN_GB) {
          result.warnings.push(`Docker disk usage is ${result.diskUsage.totalGB}GB (threshold: ${DOCKER.MAINTENANCE.DISK_WARN_GB}GB)`);
        }
      } catch (e) {
        result.warnings.push(`Disk usage check failed: ${e.message}`);
      }

      // 5. Check for containers without log rotation
      try {
        const running = await docker.listContainers({ all: false });
        for (const c of running) {
          if (c.Labels?.['sami.managed'] !== 'true') continue;
          try {
            const container = docker.getContainer(c.Id);
            const info = await container.inspect();
            const logConfig = info.HostConfig?.LogConfig;
            if (!logConfig?.Config?.['max-size']) {
              result.containersWithoutLogLimits.push({
                name: c.Names[0]?.replace(/^\//, '') || c.Id.slice(0, 12),
                id: c.Id.slice(0, 12)
              });
            }
          } catch (e) {
            // Container may have stopped between list and inspect
          }
        }
        if (result.containersWithoutLogLimits.length > 0) {
          result.warnings.push(
            `${result.containersWithoutLogLimits.length} container(s) have no log rotation — restart or update them to apply log limits: ${result.containersWithoutLogLimits.map(c => c.name).join(', ')}`
          );
        }
      } catch (e) {
        result.warnings.push(`Log config check failed: ${e.message}`);
      }

      result.spaceReclaimed.total =
        result.spaceReclaimed.images +
        result.spaceReclaimed.containers +
        result.spaceReclaimed.buildCache;

      result.duration = Date.now() - startTime;
      this.lastRun = new Date().toISOString();
      this.lastResult = result;

      this.emit('maintenance-complete', result);
      return result;
    } catch (error) {
      result.error = error.message;
      result.duration = Date.now() - startTime;
      this.lastResult = result;
      this.emit('maintenance-failed', result);
      throw error;
    }
  }

  /** Get Docker disk usage snapshot (callable on demand) */
  async getDiskUsage() {
    try {
      const df = await docker.df();
      const images = { count: (df.Images || []).length, sizeBytes: (df.Images || []).reduce((sum, i) => sum + (i.Size || 0), 0) };
      const containers = { count: (df.Containers || []).length, sizeBytes: (df.Containers || []).reduce((sum, c) => sum + (c.SizeRw || 0), 0) };
      const volumes = { count: (df.Volumes?.Volumes || []).length, sizeBytes: (df.Volumes?.Volumes || []).reduce((sum, v) => sum + (v.UsageData?.Size || 0), 0) };
      const buildCache = { count: (df.BuildCache || []).length, sizeBytes: (df.BuildCache || []).reduce((sum, b) => sum + (b.Size || 0), 0) };
      const totalBytes = images.sizeBytes + containers.sizeBytes + volumes.sizeBytes + buildCache.sizeBytes;
      return { images, containers, volumes, buildCache, totalBytes, totalGB: +(totalBytes / (1024 ** 3)).toFixed(2) };
    } catch (e) {
      return null;
    }
  }

  getStatus() {
    return {
      running: this.running,
      lastRun: this.lastRun,
      lastResult: this.lastResult
    };
  }
}

module.exports = new DockerMaintenance();
