import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import * as si from 'systeminformation';

export type ServiceStatusType = 'ACTIVE' | 'IDLE' | 'OFFLINE';

export interface ServiceStatus {
  service: string; // 'nestjs' | 'n8n' | 'ollama'
  pid: number | null;
  status: ServiceStatusType;
  cpu: number;    // % usage
  memory: number; // bytes
}

export interface SystemMetrics {
  cpuLoad: number;      // Total system CPU %
  memUsed: number;      // Total system RAM used (bytes)
  memTotal: number;     // Total system RAM (bytes)
  uptime: number;       // System uptime in seconds
  services: ServiceStatus[];
  timestamp: Date;
}

// Keep legacy alias so the Gateway compiles without changes
export type MonitorPayload = SystemMetrics;

const normalizeCommand = (cmd: string | null | undefined): string => {
  if (!cmd) return '';
  return cmd.toLowerCase().replace(/\\/g, '/').trim();
};

// n8n is only monitorable when running locally — skip process tracking if the webhook points elsewhere
const N8N_IS_LOCAL = (process.env['N8N_WEBHOOK_URL'] ?? '').toLowerCase().includes('localhost');

// Matcher functions — nestjs uses PID identity; others use .some() over signature arrays for resilience
const SERVICE_MATCHERS: Record<string, (p: si.Systeminformation.ProcessesProcessData) => boolean> = {
  nestjs: (p) => p.pid === process.pid,
  n8n:    (p) => { if (!N8N_IS_LOCAL) return false; const cmd = normalizeCommand(p.command); return ['n8n', 'n8n.exe', 'n8n-desktop'].some(sig => cmd.includes(sig)); },
  ollama: (p) => { const cmd = normalizeCommand(p.command); return ['ollama', 'ollama_llama_server'].some(sig => cmd.includes(sig)); },
};

@Injectable()
export class SurveillanceService implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(SurveillanceService.name);

  private _latestMetrics: SystemMetrics | null = null;
  private onMetricsUpdate: ((metrics: SystemMetrics) => void) | null = null;
  private pollInterval: NodeJS.Timeout | null = null;

  // ─── Lifecycle ─────────────────────────────────────────────────────────────

  onModuleInit() {
    this.logger.log('SurveillanceService booting — starting 500ms poll loop');
    this.pollInterval = setInterval(() => this.tick(), 500);
  }

  onModuleDestroy() {
    if (this.pollInterval) clearInterval(this.pollInterval);
    this.logger.log('SurveillanceService stopped');
  }

  // ─── Public API ────────────────────────────────────────────────────────────

  registerMetricsCallback(cb: (metrics: SystemMetrics) => void) {
    this.onMetricsUpdate = cb;
  }

  getLatestMetrics(): SystemMetrics | null {
    return this._latestMetrics;
  }

  // ─── Poll Tick ─────────────────────────────────────────────────────────────

  private async tick() {
    const [loadData, memData] = await Promise.all([si.currentLoad(), si.mem()]);

    const cpuLoad = parseFloat(loadData.currentLoad.toFixed(2));
    const memUsed = memData.used;
    const memTotal = memData.total;
    const uptime = Math.floor(si.time().uptime ?? 0);

    let processList: si.Systeminformation.ProcessesProcessData[] = [];
    try {
      const result = await si.processes();
      processList = result.list ?? [];
    } catch {
      // Fall through — processList stays empty, services will be offline
    }

    const services = this.buildServiceStatuses(processList);

    const metrics: SystemMetrics = {
      cpuLoad,
      memUsed,
      memTotal,
      uptime,
      services,
      timestamp: new Date(),
    };

    this._latestMetrics = metrics;

    if (this.onMetricsUpdate) {
      this.onMetricsUpdate(metrics);
    }
  }

  // ─── Helpers ───────────────────────────────────────────────────────────────

  private buildServiceStatuses(
    processList: si.Systeminformation.ProcessesProcessData[],
  ): ServiceStatus[] {
    const ACTIVE_CPU_THRESHOLD = 1.0;

    const statuses = Object.entries(SERVICE_MATCHERS).map(([serviceName, matcher]) => {
      if (processList.length === 0) {
        return { service: serviceName, pid: null, status: 'OFFLINE' as ServiceStatusType, cpu: 0, memory: 0 };
      }

      const matches = processList.filter(matcher);

      if (matches.length === 0) {
        return { service: serviceName, pid: null, status: 'OFFLINE' as ServiceStatusType, cpu: 0, memory: 0 };
      }

      // Aggregate CPU and RSS memory across all matching processes
      const { totalCpu, totalMem, firstPid } = matches.reduce(
        (acc, p) => ({
          totalCpu:  acc.totalCpu + (p.cpu ?? 0),
          totalMem:  acc.totalMem + (p.memRss ?? 0),
          firstPid:  acc.firstPid ?? p.pid,
        }),
        { totalCpu: 0, totalMem: 0, firstPid: null as number | null },
      );

      const status: ServiceStatusType = totalCpu > ACTIVE_CPU_THRESHOLD ? 'ACTIVE' : 'IDLE';

      return {
        service: serviceName,
        pid:     firstPid,
        status,
        cpu:     parseFloat(totalCpu.toFixed(2)),
        memory:  totalMem,
      };
    });

    // Verification debug log — one line per service per tick
    for (const s of statuses) {
      this.logger.debug(
        `[aggregator] ${s.service.padEnd(8)} | status=${s.status.padEnd(7)} | cpu=${s.cpu.toFixed(2).padStart(6)}% | mem=${s.memory} bytes | pid=${s.pid ?? 'none'}`,
      );
    }

    return statuses;
  }
}
