import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { registerGracefulShutdown } from "./shutdown.js";
import type { Logger } from "@/observability/logger/types.js";

function createMockLogger(): Logger & { flush: ReturnType<typeof vi.fn> } {
  const logger = {
    debug: vi.fn(),
    info: vi.fn(),
    warn: vi.fn(),
    error: vi.fn(),
    child: vi.fn(),
    flush: vi.fn().mockResolvedValue(undefined),
  };
  logger.child.mockReturnValue(logger);
  return logger as Logger & { flush: ReturnType<typeof vi.fn> };
}

function createMockServer() {
  return {
    close: vi.fn((cb: () => void) => cb()),
  };
}

describe("registerGracefulShutdown", () => {
  /** Captured SIGTERM handlers from process.on */
  let capturedHandlers: Array<() => void>;
  let exitCalls: number[];

  beforeEach(() => {
    capturedHandlers = [];
    exitCalls = [];

    // Intercept process.on("SIGTERM", handler) without fighting spy types
    process.on = ((event: string, listener: () => void) => {
      if (event === "SIGTERM") {
        capturedHandlers.push(listener);
      }
      return process;
    }) as typeof process.on;

    // Intercept process.exit
    process.exit = ((code?: number) => {
      exitCalls.push(code ?? 0);
    }) as typeof process.exit;

    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.restoreAllMocks();
    vi.useRealTimers();
  });

  function triggerSigterm(): void {
    expect(capturedHandlers.length).toBeGreaterThan(0);
    const handler = capturedHandlers[0];
    if (handler) handler();
  }

  it("registers a SIGTERM handler", () => {
    const logger = createMockLogger();
    const server = createMockServer();

    registerGracefulShutdown({ logger, server });

    expect(capturedHandlers).toHaveLength(1);
  });

  it("calls server.close on SIGTERM", async () => {
    const logger = createMockLogger();
    const server = createMockServer();

    registerGracefulShutdown({ logger, server });
    triggerSigterm();

    await vi.advanceTimersByTimeAsync(0);

    expect(server.close).toHaveBeenCalled();
  });

  it("flushes logger after server closes", async () => {
    const logger = createMockLogger();
    const server = createMockServer();

    registerGracefulShutdown({ logger, server });
    triggerSigterm();

    await vi.advanceTimersByTimeAsync(0);

    expect(logger.flush).toHaveBeenCalled();
  });

  it("exits with code 0 after successful drain", async () => {
    const logger = createMockLogger();
    const server = createMockServer();

    registerGracefulShutdown({ logger, server });
    triggerSigterm();

    await vi.advanceTimersByTimeAsync(0);

    expect(exitCalls).toContain(0);
  });

  it("force-exits with code 1 if drain times out", async () => {
    const logger = createMockLogger();
    const server = {
      close: vi.fn(), // never calls callback — simulates stuck connections
    };

    registerGracefulShutdown({ logger, server, drainTimeoutMs: 1000 });
    triggerSigterm();

    // Advance past the drain timeout
    await vi.advanceTimersByTimeAsync(1100);

    expect(exitCalls).toContain(1);
  });

  it("logs shutdown lifecycle events", async () => {
    const logger = createMockLogger();
    const server = createMockServer();

    registerGracefulShutdown({ logger, server });
    triggerSigterm();

    await vi.advanceTimersByTimeAsync(0);

    expect(logger.info).toHaveBeenCalledWith(
      "SIGTERM received, starting graceful shutdown",
      expect.any(Object),
    );
    expect(logger.info).toHaveBeenCalledWith(
      "Server closed, flushing logs",
      undefined,
    );
  });

  it("uses default drainTimeoutMs of 30000", () => {
    const logger = createMockLogger();
    const server = {
      close: vi.fn(), // never calls callback
    };

    registerGracefulShutdown({ logger, server });
    triggerSigterm();

    // Shouldn't have exited yet at 29s
    vi.advanceTimersByTime(29_000);
    expect(exitCalls).toHaveLength(0);

    // Should force-exit at 30s
    vi.advanceTimersByTime(2_000);
    expect(exitCalls).toContain(1);
  });
});
