import { createServer, type Server } from "node:http";
import type { AddressInfo } from "node:net";
import { afterAll, beforeAll, describe, expect, test } from "vitest";
import { probe, runHealthCheck } from "@/server/health/checker";

// A real local HTTP server stands in for the monitored health APIs.
let server: Server;
let base: string;
let port: number;
const hits: Record<string, number> = {};

beforeAll(async () => {
  server = createServer((req, res) => {
    const path = req.url ?? "/";
    hits[path] = (hits[path] ?? 0) + 1;
    const send = (code: number) => {
      res.statusCode = code;
      res.end();
    };
    if (path === "/ok") return send(200);
    if (path === "/created") return send(201);
    if (path === "/503") return send(503);
    if (path === "/404") return send(404);
    if (path === "/redirect") {
      res.setHeader("Location", "/ok");
      return send(302);
    }
    if (path === "/slow") return void setTimeout(() => send(200), 3000);
    if (path === "/sluggish") return void setTimeout(() => send(200), 700);
    if (path === "/reset") return void req.socket.destroy();
    if (path.startsWith("/flaky")) return send(hits[path] % 2 === 1 ? 500 : 200);
    send(418);
  });
  await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
  port = (server.address() as AddressInfo).port;
  base = `http://127.0.0.1:${port}`;
});

afterAll(() => {
  server.closeAllConnections();
  server.close();
});

const target = (url: string, expectedHttpStatus = 200, timeoutSeconds = 5, slowThresholdSeconds = 4) => ({
  url,
  expectedHttpStatus,
  timeoutSeconds,
  slowThresholdSeconds,
});

describe("probe: Working", () => {
  test("expected status → WORKING with a response time and no error", async () => {
    const outcome = await probe(target(`${base}/ok`));
    expect(outcome).toMatchObject({ status: "WORKING", httpStatusCode: 200, errorType: null, errorMessage: null });
    expect(outcome.responseTimeMs).toBeGreaterThanOrEqual(0);
  });
  test("a non-200 expected status is honoured", async () => {
    expect(await probe(target(`${base}/created`, 201))).toMatchObject({ status: "WORKING", httpStatusCode: 201 });
  });
});

describe("probe: Slow Response", () => {
  test("expected status but slower than the threshold → SLOW, with the time and a note", async () => {
    const outcome = await probe(target(`${base}/sluggish`, 200, 5, 0.5));
    expect(outcome).toMatchObject({ status: "SLOW", httpStatusCode: 200, errorType: "SLOW_RESPONSE" });
    expect(outcome.responseTimeMs).toBeGreaterThanOrEqual(600);
    expect(outcome.errorMessage).toMatch(/^Slow response: 0\.\ds \(threshold 0\.5s\)$/);
  });
  test("the same response within the threshold is WORKING", async () => {
    expect(await probe(target(`${base}/sluggish`, 200, 5, 2))).toMatchObject({ status: "WORKING", errorType: null });
  });
  test("a wrong status is Not Working even when it is also slow", async () => {
    expect(await probe(target(`${base}/sluggish`, 204, 5, 0.5))).toMatchObject({ status: "NOT_WORKING", errorType: "HTTP_STATUS" });
  });
  test("the timeout still wins over the threshold", async () => {
    expect(await probe(target(`${base}/slow`, 200, 1, 0.5))).toMatchObject({ status: "NOT_WORKING", errorType: "TIMEOUT" });
  });
});

describe("probe: Not Working", () => {
  test.each([
    ["/503", 503, "HTTP 503 Service Unavailable (expected 200)"],
    ["/404", 404, "HTTP 404 Not Found (expected 200)"],
  ])("%s", async (path, code, message) => {
    expect(await probe(target(base + path))).toMatchObject({
      status: "NOT_WORKING",
      httpStatusCode: code,
      errorType: "HTTP_STATUS",
      errorMessage: message,
    });
  });

  test("unexpected status: 200 when 204 is expected", async () => {
    expect(await probe(target(`${base}/ok`, 204))).toMatchObject({
      status: "NOT_WORKING",
      errorType: "HTTP_STATUS",
      errorMessage: "HTTP 200 OK (expected 204)",
    });
  });

  test("redirects are not followed", async () => {
    const targetHitsBefore = hits["/ok"] ?? 0;
    const outcome = await probe(target(`${base}/redirect`));
    expect(outcome).toMatchObject({ status: "NOT_WORKING", httpStatusCode: 302, errorType: "HTTP_STATUS" });
    expect(outcome.errorMessage).toContain("redirect not followed");
    expect(hits["/ok"] ?? 0).toBe(targetHitsBefore); // the redirect target was never requested
  });

  test("timeout", async () => {
    const outcome = await probe(target(`${base}/slow`, 200, 1));
    expect(outcome).toMatchObject({ status: "NOT_WORKING", httpStatusCode: null, errorType: "TIMEOUT" });
    expect(outcome.errorMessage).toBe("Request Timeout (no response within 1s)");
    expect(outcome.responseTimeMs).toBeGreaterThanOrEqual(900);
    expect(outcome.responseTimeMs).toBeLessThan(2500);
  });

  test("connection refused", async () => {
    const closed = createServer();
    await new Promise<void>((resolve) => closed.listen(0, "127.0.0.1", resolve));
    const closedPort = (closed.address() as AddressInfo).port;
    await new Promise((resolve) => closed.close(resolve));

    expect(await probe(target(`http://127.0.0.1:${closedPort}/health`))).toMatchObject({
      status: "NOT_WORKING",
      errorType: "CONNECTION",
      errorMessage: "Connection refused (ECONNREFUSED)",
      responseTimeMs: null,
    });
  });

  test("connection reset", async () => {
    expect(await probe(target(`${base}/reset`))).toMatchObject({ status: "NOT_WORKING", errorType: "CONNECTION" });
  });

  test("DNS failure", async () => {
    const outcome = await probe(target("http://bts-no-such-host.invalid/health"));
    expect(outcome).toMatchObject({ status: "NOT_WORKING", errorType: "DNS" });
    expect(outcome.errorMessage).toContain("DNS lookup failed");
  });

  test("SSL/TLS failure (HTTPS against a plain-HTTP port)", async () => {
    const outcome = await probe(target(`https://127.0.0.1:${port}/ok`));
    expect(outcome).toMatchObject({ status: "NOT_WORKING", errorType: "TLS" });
    expect(outcome.errorMessage).toMatch(/^SSL\/TLS error/);
    expect(outcome.errorMessage).not.toContain("\n");
  });

  test("other network errors keep a readable message", async () => {
    // Port 9 is refused by fetch itself ("bad port") — no error code to classify.
    expect(await probe(target("http://127.0.0.1:9/health"))).toMatchObject({
      status: "NOT_WORKING",
      errorType: "UNKNOWN",
      errorMessage: "Network error: bad port",
    });
  });

  test("never throws on garbage input or blocked hosts", async () => {
    expect(await probe(target("not a url"))).toMatchObject({ status: "NOT_WORKING", errorMessage: "Invalid Health API URL" });
    expect(await probe(target("file:///etc/passwd"))).toMatchObject({ status: "NOT_WORKING" });
    expect(await probe(target("http://169.254.169.254/latest/meta-data"))).toMatchObject({
      status: "NOT_WORKING",
      errorMessage: "Health API host is not allowed",
    });
  });
});

describe("runHealthCheck: single retry", () => {
  test("a healthy app is requested once", async () => {
    const before = hits["/ok"] ?? 0;
    const result = await runHealthCheck(target(`${base}/ok`), 0);
    expect(result).toMatchObject({ status: "WORKING", attempts: 1 });
    expect(hits["/ok"]).toBe(before + 1);
    expect(result.checkedAt).toBeInstanceOf(Date);
  });

  test("a momentary blip is absorbed: 500 then 200 → WORKING, attempts 2", async () => {
    const result = await runHealthCheck(target(`${base}/flaky-blip`), 0);
    expect(result).toMatchObject({ status: "WORKING", httpStatusCode: 200, attempts: 2 });
    expect(hits["/flaky-blip"]).toBe(2);
  });

  test("a slow response is not a failure, so it is not retried", async () => {
    const before = hits["/sluggish"] ?? 0;
    const result = await runHealthCheck(target(`${base}/sluggish`, 200, 5, 0.5), 0);
    expect(result).toMatchObject({ status: "SLOW", attempts: 1 });
    expect(hits["/sluggish"]).toBe(before + 1);
  });

  test("a real failure is retried exactly once, then reported", async () => {
    const before = hits["/503"] ?? 0;
    const result = await runHealthCheck(target(`${base}/503`), 0);
    expect(result).toMatchObject({ status: "NOT_WORKING", httpStatusCode: 503, attempts: 2 });
    expect(hits["/503"]).toBe(before + 2);
  });

  test("waits for the configured delay before retrying", async () => {
    const started = Date.now();
    await runHealthCheck(target(`${base}/404`), 300);
    expect(Date.now() - started).toBeGreaterThanOrEqual(290);
  });
});
