import { Injectable, Logger } from "@nestjs/common";
import * as os from "os";

export interface ResourceMetrics {
  timestamp: number;
  memory: {
    rss: number;
    heapTotal: number;
    heapUsed: number;
    external: number;
    arrayBuffers?: number;
  };
  cpu: {
    user: number;
    system: number;
    percent: number;
  };
  loadAvg: number[];
  freeMemory: number;
}

export interface RequestMetrics {
  timestamp: number;
  method: string;
  path: string;
  statusCode: number;
  responseTime: number;
}

@Injectable()
export class MetricsService {
  private readonly logger = new Logger(MetricsService.name);
  private readonly maxHistoryPoints = 100;
  private readonly resourceMetrics: ResourceMetrics[] = [];
  private readonly requestMetrics: RequestMetrics[] = [];

  private maxMemoryUsage = {
    rss: 0,
    heapTotal: 0,
    heapUsed: 0,
    external: 0,
    arrayBuffers: 0,
  };

  private minMemoryUsage = {
    rss: Number.MAX_SAFE_INTEGER,
    heapTotal: Number.MAX_SAFE_INTEGER,
    heapUsed: Number.MAX_SAFE_INTEGER,
    external: Number.MAX_SAFE_INTEGER,
    arrayBuffers: Number.MAX_SAFE_INTEGER,
  };

  private maxCpuUsage = {
    user: 0,
    system: 0,
    percent: 0,
  };

  private requestStats = {
    total: 0,
    byMethod: {} as Record<string, number>,
    byStatusCode: {} as Record<string, number>,
    avgResponseTime: 0,
    maxResponseTime: 0,
    minResponseTime: Number.MAX_SAFE_INTEGER,
  };

  private lastCpuUsage = process.cpuUsage();
  private lastCpuUsageTime = Date.now();

  constructor() {
    setInterval(() => this.collectResourceMetrics(), 5000);
    this.logger.log("Metrics service initialized");
  }

  recordRequest(
    method: string,
    path: string,
    statusCode: number,
    responseTime: number,
  ): void {
    const timestamp = Date.now();

    this.requestMetrics.push({
      timestamp,
      method,
      path,
      statusCode,
      responseTime,
    });
    if (this.requestMetrics.length > this.maxHistoryPoints) {
      this.requestMetrics.shift();
    }

    this.requestStats.total++;

    this.requestStats.byMethod[method] =
      (this.requestStats.byMethod[method] || 0) + 1;

    const statusRange = `${Math.floor(statusCode / 100)}xx`;
    this.requestStats.byStatusCode[statusRange] =
      (this.requestStats.byStatusCode[statusRange] || 0) + 1;
    this.requestStats.byStatusCode[statusCode.toString()] =
      (this.requestStats.byStatusCode[statusCode.toString()] || 0) + 1;

    const prevTotal =
      this.requestStats.avgResponseTime * (this.requestStats.total - 1);
    this.requestStats.avgResponseTime =
      (prevTotal + responseTime) / this.requestStats.total;
    this.requestStats.maxResponseTime = Math.max(
      this.requestStats.maxResponseTime,
      responseTime,
    );
    this.requestStats.minResponseTime = Math.min(
      this.requestStats.minResponseTime,
      responseTime,
    );
  }

  private collectResourceMetrics(): void {
    try {
      const memoryUsage = process.memoryUsage();
      const currentTime = Date.now();
      const cpuUsage = process.cpuUsage();

      const userDiff = cpuUsage.user - this.lastCpuUsage.user;
      const systemDiff = cpuUsage.system - this.lastCpuUsage.system;
      const timeDiff = currentTime - this.lastCpuUsageTime;

      const cpuPercent = (userDiff + systemDiff) / (timeDiff * 10);

      const metrics: ResourceMetrics = {
        timestamp: currentTime,
        memory: {
          rss: memoryUsage.rss,
          heapTotal: memoryUsage.heapTotal,
          heapUsed: memoryUsage.heapUsed,
          external: memoryUsage.external,
          arrayBuffers: memoryUsage.arrayBuffers,
        },
        cpu: {
          user: cpuUsage.user,
          system: cpuUsage.system,
          percent: cpuPercent,
        },
        loadAvg: os.loadavg(),
        freeMemory: os.freemem(),
      };

      this.maxMemoryUsage.rss = Math.max(
        this.maxMemoryUsage.rss,
        memoryUsage.rss,
      );
      this.maxMemoryUsage.heapTotal = Math.max(
        this.maxMemoryUsage.heapTotal,
        memoryUsage.heapTotal,
      );
      this.maxMemoryUsage.heapUsed = Math.max(
        this.maxMemoryUsage.heapUsed,
        memoryUsage.heapUsed,
      );
      this.maxMemoryUsage.external = Math.max(
        this.maxMemoryUsage.external,
        memoryUsage.external,
      );
      this.maxMemoryUsage.arrayBuffers = Math.max(
        this.maxMemoryUsage.arrayBuffers || 0,
        memoryUsage.arrayBuffers || 0,
      );

      this.minMemoryUsage.rss = Math.min(
        this.minMemoryUsage.rss,
        memoryUsage.rss,
      );
      this.minMemoryUsage.heapTotal = Math.min(
        this.minMemoryUsage.heapTotal,
        memoryUsage.heapTotal,
      );
      this.minMemoryUsage.heapUsed = Math.min(
        this.minMemoryUsage.heapUsed,
        memoryUsage.heapUsed,
      );
      this.minMemoryUsage.external = Math.min(
        this.minMemoryUsage.external,
        memoryUsage.external,
      );
      this.minMemoryUsage.arrayBuffers = Math.min(
        this.minMemoryUsage.arrayBuffers,
        memoryUsage.arrayBuffers || 0,
      );

      this.maxCpuUsage.user = Math.max(this.maxCpuUsage.user, cpuUsage.user);
      this.maxCpuUsage.system = Math.max(
        this.maxCpuUsage.system,
        cpuUsage.system,
      );
      this.maxCpuUsage.percent = Math.max(this.maxCpuUsage.percent, cpuPercent);

      this.resourceMetrics.push(metrics);
      if (this.resourceMetrics.length > this.maxHistoryPoints) {
        this.resourceMetrics.shift();
      }

      this.lastCpuUsage = cpuUsage;
      this.lastCpuUsageTime = currentTime;
    } catch (error) {
      this.logger.error(`Error collecting metrics: ${error.message}`);
    }
  }

  getMetrics() {
    return {
      current:
        this.resourceMetrics.length > 0
          ? this.resourceMetrics[this.resourceMetrics.length - 1]
          : null,
      max: {
        memory: this.maxMemoryUsage,
        cpu: this.maxCpuUsage,
      },
      min: {
        memory: this.minMemoryUsage,
      },
      requests: {
        stats: this.requestStats,
        recent: this.requestMetrics.slice(-10),
      },
      history: {
        resources: this.resourceMetrics,
      },
    };
  }
}
