import {
  Controller,
  Get,
  VERSION_NEUTRAL,
  Version,
  Query,
  Inject,
  Optional,
  UseGuards,
} from "@nestjs/common";
import {
  ApiTags,
  ApiOperation,
  ApiQuery,
  ApiResponse,
  ApiSecurity,
} from "@nestjs/swagger";
import {
  HealthCheck,
  HealthCheckService,
  MemoryHealthIndicator,
  DiskHealthIndicator,
} from "@nestjs/terminus";
import { HealthService } from "./health.service";
import { MetricsService } from "./metrics.service";
import { HealthModuleOptions, MODULE_OPTIONS_TOKEN } from "./health.module";
import { HealthAuthGuard } from "./health-auth.guard";

@ApiTags("health")
@ApiSecurity("auth-key")
@Controller("health")
export class HealthController {
  private readonly options: any;

  constructor(
    private health: HealthCheckService,
    private memory: MemoryHealthIndicator,
    private disk: DiskHealthIndicator,
    private healthService: HealthService,
    private metricsService: MetricsService,
    @Optional() @Inject(MODULE_OPTIONS_TOKEN) options: HealthModuleOptions,
  ) {
    this.options = options || {
      enableMemoryMonitoring: true,
      enableDiskMonitoring: true,
      diskPath: "/",
      diskThresholdPercent: 0.75,
      routePrefix: "health",
    };
  }

  @Get()
  @UseGuards(HealthAuthGuard)
  @HealthCheck()
  @ApiOperation({ summary: "Check application health status" })
  @ApiResponse({ status: 200, description: "Application is healthy" })
  @ApiResponse({ status: 503, description: "Application is not healthy" })
  check() {
    const checks = [() => this.healthService.isHealthy()];

    // Add memory checks if enabled
    if (this.options.enableMemoryMonitoring) {
      checks.push(() =>
        this.memory.checkHeap("memory_heap", 150 * 1024 * 1024),
      );
      checks.push(() => this.memory.checkRSS("memory_rss", 150 * 1024 * 1024));
    }

    // Add disk checks if enabled
    if (this.options.enableDiskMonitoring) {
      checks.push(() =>
        this.disk.checkStorage("disk", {
          path: this.options.diskPath,
          thresholdPercent: this.options.diskThresholdPercent,
        }),
      );
    }

    return this.health.check(checks);
  }

  @Get("live")
  @UseGuards(HealthAuthGuard)
  @HealthCheck()
  @ApiOperation({ summary: "Liveness probe for container orchestration" })
  @ApiResponse({ status: 200, description: "Application is alive" })
  @ApiResponse({ status: 503, description: "Application is not alive" })
  liveness() {
    return this.health.check([() => this.healthService.isHealthy()]);
  }

  @Get("ready")
  @UseGuards(HealthAuthGuard)
  @HealthCheck()
  @ApiOperation({ summary: "Readiness probe for container orchestration" })
  @ApiResponse({
    status: 200,
    description: "Application is ready to accept traffic",
  })
  @ApiResponse({ status: 503, description: "Application is not ready" })
  readiness() {
    return this.health.check([
      () => this.healthService.isHealthy(),
      () => this.memory.checkHeap("memory_heap", 300 * 1024 * 1024),
    ]);
  }

  @Get("stats")
  @UseGuards(HealthAuthGuard)
  @Version(VERSION_NEUTRAL)
  @ApiOperation({ summary: "Get detailed application statistics" })
  @ApiQuery({
    name: "includeHistory",
    required: false,
    type: "boolean",
    description: "Whether to include historical metrics data",
  })
  @ApiResponse({
    status: 200,
    description: "Application statistics retrieved successfully",
  })
  async getDetailedStats(@Query("includeHistory") includeHistory: string) {
    const startTime = process.uptime();
    const memoryStats = await this.healthService.getMemoryStats();

    // Get health status safely with try/catch to handle possible HealthCheckError
    let healthStatus = "unhealthy";
    let healthDetails = {};
    try {
      const health = await this.healthService.isHealthy();
      healthStatus = "healthy";
      healthDetails = health;
    } catch (error) {
      healthStatus = "unhealthy";
      if (error.response) {
        healthDetails = error.response;
      }
    }

    const diskStats = this.options.enableDiskMonitoring
      ? await this.disk.checkStorage("disk", {
          path: this.options.diskPath,
          thresholdPercent: this.options.diskThresholdPercent,
        })
      : { disk: { status: "unknown" } };

    const os = require("os");
    const cpuUsage = process.cpuUsage();
    const systemUptime = os.uptime();
    const loadAvg = os.loadavg();

    const metrics = this.metricsService.getMetrics();

    return {
      status: healthStatus,
      timestamp: new Date().toISOString(),
      version: process.env.npm_package_version || "1.0.0",
      process: {
        pid: process.pid,
        ppid: process.ppid,
        title: process.title,
        uptime: {
          seconds: startTime,
          formatted: this.formatUptime(startTime),
        },
        memoryUsage: {
          current: {
            rss: this.formatBytes(process.memoryUsage().rss),
            heapTotal: this.formatBytes(process.memoryUsage().heapTotal),
            heapUsed: this.formatBytes(process.memoryUsage().heapUsed),
            external: this.formatBytes(process.memoryUsage().external || 0),
            arrayBuffers: this.formatBytes(
              process.memoryUsage().arrayBuffers || 0,
            ),
          },
          max: {
            rss: this.formatBytes(metrics.max.memory.rss),
            heapTotal: this.formatBytes(metrics.max.memory.heapTotal),
            heapUsed: this.formatBytes(metrics.max.memory.heapUsed),
            external: this.formatBytes(metrics.max.memory.external),
            arrayBuffers: this.formatBytes(
              metrics.max.memory.arrayBuffers || 0,
            ),
          },
          min: {
            rss: this.formatBytes(metrics.min.memory.rss),
            heapTotal: this.formatBytes(metrics.min.memory.heapTotal),
            heapUsed: this.formatBytes(metrics.min.memory.heapUsed),
            external: this.formatBytes(metrics.min.memory.external),
            arrayBuffers: this.formatBytes(
              metrics.min.memory.arrayBuffers || 0,
            ),
          },
        },
        cpuUsage: {
          current: {
            user: cpuUsage.user,
            system: cpuUsage.system,
            percent: metrics.current?.cpu.percent.toFixed(2) + "%" || "0%",
          },
          max: {
            user: metrics.max.cpu.user,
            system: metrics.max.cpu.system,
            percent: metrics.max.cpu.percent.toFixed(2) + "%",
          },
        },
      },
      system: {
        platform: process.platform,
        arch: process.arch,
        nodeVersion: process.version,
        hostname: os.hostname(),
        uptime: {
          seconds: systemUptime,
          formatted: this.formatUptime(systemUptime),
        },
        loadAvg: os.loadavg(),
        totalMemory: this.formatBytes(os.totalmem()),
        freeMemory: this.formatBytes(os.freemem()),
        cpus: os.cpus().length,
      },
      disk: diskStats,
      environment: process.env.NODE_ENV || "development",
      requests: {
        total: metrics.requests.stats.total,
        responseTime: {
          avg: metrics.requests.stats.avgResponseTime.toFixed(2) + "ms",
          max: metrics.requests.stats.maxResponseTime + "ms",
          min:
            metrics.requests.stats.minResponseTime === Number.MAX_SAFE_INTEGER
              ? "0ms"
              : metrics.requests.stats.minResponseTime + "ms",
        },
        byMethod: metrics.requests.stats.byMethod,
        byStatusCode: metrics.requests.stats.byStatusCode,
        recent: metrics.requests.recent,
      },
      ...(includeHistory === "true" && { history: metrics.history }),
    };
  }

  private formatBytes(bytes: number): string {
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }

  private formatUptime(seconds: number): string {
    const days = Math.floor(seconds / (3600 * 24));
    const hours = Math.floor((seconds % (3600 * 24)) / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    const secs = Math.floor(seconds % 60);

    return `${days}d ${hours}h ${minutes}m ${secs}s`;
  }
}
