// health.controller.ts

import { Controller, Get, HttpStatus, Res } from "@nestjs/common";
import { ApiOperation, ApiTags } from "@nestjs/swagger";
import { InjectDataSource } from "@nestjs/typeorm";
import { Public } from "@edr/api-common";
import { Response } from "express";
import { DataSource } from "typeorm";

import { EmailClientService } from "../notifications/email-client.service";
import { SmsClientService } from "../notifications/sms-client.service";

type CheckStatus = "ok" | "error" | "unknown";

/**
 * Readiness normally stays green when only the broker is down.
 *
 * A 503 pulls the pod out of the load balancer, which would take booking,
 * tracking and billing offline because SMS is unreachable — a strictly worse
 * outcome than degraded notifications. The broker check is therefore reported,
 * not enforced, and `READINESS_REQUIRES_BROKER=true` opts into hard-failing for
 * deployments where a silent OTP black hole is the greater risk.
 */
const READINESS_REQUIRES_BROKER =
  process.env.READINESS_REQUIRES_BROKER === "true";

@ApiTags("Health")
@Controller("health")
export class HealthController {
  constructor(
    @InjectDataSource()
    private readonly dataSource: DataSource,
    private readonly smsClient: SmsClientService,
    private readonly emailClient: EmailClientService,
  ) {}

  @Get()
  @Public()
  @ApiOperation({ summary: "Liveness probe" })
  liveness() {
    return { status: "ok", timestamp: new Date().toISOString() };
  }

  @Get("ready")
  @Public()
  @ApiOperation({
    summary:
      "Readiness probe — database plus SMS/email broker connectivity. Broker failures report as degraded unless READINESS_REQUIRES_BROKER=true.",
  })
  async readiness(@Res() res: Response) {
    const startedAt = Date.now();

    let database: { status: CheckStatus; latencyMs: number; error?: string };
    try {
      await this.dataSource.query("SELECT 1");
      database = { status: "ok", latencyMs: Date.now() - startedAt };
    } catch (error) {
      database = {
        status: "error",
        latencyMs: Date.now() - startedAt,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }

    // `null` from the client means the connection manager was not reachable
    // through Nest's internals — surfaced as "unknown" so a shape change in
    // @nestjs/microservices degrades to honest ignorance, not a false "ok".
    const toStatus = (connected: boolean | null): CheckStatus =>
      connected === null ? "unknown" : connected ? "ok" : "error";

    const broker = {
      sms: { status: toStatus(this.smsClient.brokerConnected) },
      email: { status: toStatus(this.emailClient.brokerConnected) },
      // Every OTP, and every booking/billing notification, publishes through
      // these. `error` here means codes are being generated and silently dropped.
      enabled: process.env.RABBITMQ_ENABLED !== "false",
    };

    const brokerDown =
      broker.sms.status === "error" || broker.email.status === "error";
    const failed =
      database.status === "error" ||
      (READINESS_REQUIRES_BROKER && brokerDown);

    const status = failed ? "error" : brokerDown ? "degraded" : "ok";

    return res
      .status(failed ? HttpStatus.SERVICE_UNAVAILABLE : HttpStatus.OK)
      .json({
        status,
        timestamp: new Date().toISOString(),
        checks: { database, broker },
      });
  }

  @Get("info")
  @Public()
  @ApiOperation({ summary: "App info — version, environment, uptime" })
  info() {
    return {
      name: "edr-freight-api",
      version: process.env.npm_package_version ?? "1.0.0",
      environment: process.env.NODE_ENV ?? "development",
      uptimeSeconds: Math.floor(process.uptime()),
      timestamp: new Date().toISOString(),
    };
  }
}
