import { describe, expect, test } from "bun:test";
import { lstatSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { createDeployStatusAdapter } from "./deploy-status";

const FIXTURE_DIR = join(import.meta.dir, "../../test/fixtures/deploy-status");
const QUEUE_DIR = join(FIXTURE_DIR, "queued");
const LOCK_PATH = join(FIXTURE_DIR, "deploy.lock");
const NOW_MS = Date.parse("2026-08-12T10:04:00.000Z");
const LOCK_STAT = { mtimeMs: NOW_MS, dev: 0x0801, ino: 42, isFile: () => true };
const HELD_LOCKS = "7: FLOCK ADVISORY WRITE 4242 08:01:42 0 EOF\n";

function missingPath(): NodeJS.ErrnoException {
  return Object.assign(new Error("ENOENT"), { code: "ENOENT" });
}

const DEPLOY_DIR = join(FIXTURE_DIR, "deploy-clone-absent");
const PROGRESS_PATH = join(FIXTURE_DIR, "deploy-status.json");

/** Serves /proc/locks only; every other path is absent, as on a machine with no state file. */
function locksOnly(locks: string) {
  return (path: string) => {
    if (path === "/proc/locks") return locks;
    throw missingPath();
  };
}

function progressRecord(overrides: Record<string, unknown> = {}) {
  return JSON.stringify({
    schema: 1,
    state: "running",
    step: "building-web",
    detail: "building the web app",
    sha: "e99f2d4f",
    pid: 4242,
    started_at: Math.floor(Date.parse("2026-08-12T10:00:00.000Z") / 1000),
    updated_at: Math.floor(Date.parse("2026-08-12T10:03:00.000Z") / 1000),
    ...overrides,
  });
}

function adapter(overrides: Parameters<typeof createDeployStatusAdapter>[0] = {}) {
  return createDeployStatusAdapter({
    queueDir: QUEUE_DIR,
    lockPath: LOCK_PATH,
    deployDir: DEPLOY_DIR,
    progressPath: PROGRESS_PATH,
    now: () => NOW_MS,
    readdirImpl: () => ["req-100-7"],
    readFileImpl: (path) => path === "/proc/locks" ? "" : readFileSync(path, "utf8"),
    lstatImpl: (path) => path === LOCK_PATH ? LOCK_STAT : lstatSync(path),
    gitRevImpl: () => null,
    controllerToken: "",
    ...overrides,
  });
}

describe("createDeployStatusAdapter", () => {
  test("reports queued requests with identity, age, and observed timestamp", async () => {
    const result = await adapter({
      lstatImpl: (path) => path === LOCK_PATH
        ? LOCK_STAT
        : { ...LOCK_STAT, mtimeMs: Date.parse("2026-08-12T10:01:00.000Z") },
    }).poll();

    expect(result.panels).toHaveLength(1);
    expect(result.panels[0]).toMatchObject({
      id: "deploy-status",
      ts: "2026-08-12T10:04:00.000Z",
      data: {
        state: "queued",
        complete: true,
        queue: {
          depth: 1,
          oldestAgeMs: 180_000,
          requests: [{ id: "req-100-7", observedAt: "2026-08-12T10:01:00.000Z" }],
        },
        operation: null,
        holder: null,
        latestEvent: null,
        latestProgressAt: null,
        reason: null,
      },
    });
  });

  test("reports held lock without lifecycle evidence as incomplete unknown", async () => {
    const result = await adapter({
      readdirImpl: () => ["req-100-7"],
      readFileImpl: locksOnly(HELD_LOCKS),
      lstatImpl: () => LOCK_STAT,
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      queue: { depth: 1 },
      operation: null,
      holder: "pid:4242",
      latestEvent: null,
      latestProgressAt: null,
      reason: "active deploy has no authoritative operation identity or lifecycle record",
    });
  });

  test("does not call an active holder stalled without authoritative progress evidence", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      readFileImpl: locksOnly(HELD_LOCKS),
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      holder: "pid:4242",
      latestProgressAt: null,
    });
  });

  test("reports a lock-held deploy publishing progress as running with its step", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      progressPath: PROGRESS_PATH,
      readFileImpl: (path) => {
        if (path === "/proc/locks") return HELD_LOCKS;
        if (path === PROGRESS_PATH) return progressRecord();
        throw missingPath();
      },
      lstatImpl: () => LOCK_STAT,
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "running",
      complete: true,
      reason: null,
      operation: "building the web app",
      holder: "pid:4242",
      latestEvent: {
        at: "2026-08-12T10:03:00.000Z",
        type: "building-web",
        detail: "building the web app",
      },
      latestProgressAt: "2026-08-12T10:03:00.000Z",
    });
  });

  test("accepts target-bound schema-2 progress during schema-1 rollout", async () => {
    const target = "b".repeat(40);
    const result = await adapter({
      readdirImpl: () => [],
      progressPath: PROGRESS_PATH,
      readFileImpl: (path) => {
        if (path === "/proc/locks") return HELD_LOCKS;
        if (path === PROGRESS_PATH) return progressRecord({ schema: 2, target_sha: target, failure_class: "none" });
        throw missingPath();
      },
      lstatImpl: () => LOCK_STAT,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      state: "running",
      complete: true,
      latestEvent: { type: "building-web" },
    });
  });

  test("reports a running record whose lock is gone as stalled, never idle", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      progressPath: PROGRESS_PATH,
      readFileImpl: (path) => {
        if (path === "/proc/locks") return "";
        if (path === PROGRESS_PATH) return progressRecord();
        throw missingPath();
      },
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "stalled",
      complete: true,
      holder: null,
      latestProgressAt: "2026-08-12T10:03:00.000Z",
    });
  });

  test("reports a finished record with an empty queue as idle carrying its last event", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      progressPath: PROGRESS_PATH,
      readFileImpl: (path) => {
        if (path === "/proc/locks") return "";
        if (path === PROGRESS_PATH) {
          return progressRecord({ state: "finished", step: "deployed", detail: "deployed e99f2d4f" });
        }
        throw missingPath();
      },
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "idle",
      complete: true,
      operation: null,
      latestEvent: { type: "deployed", detail: "deployed e99f2d4f" },
      latestProgressAt: "2026-08-12T10:03:00.000Z",
    });
  });

  test("reports an unparseable progress record as incomplete, never as a fabricated state", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      progressPath: PROGRESS_PATH,
      readFileImpl: (path) => {
        if (path === "/proc/locks") return "";
        if (path === PROGRESS_PATH) return "not json";
        throw missingPath();
      },
    }).poll();

    const data = result.panels[0]?.data as { state?: string; complete?: boolean; reason?: string };
    expect(data.state).toBe("unknown");
    expect(data.complete).toBe(false);
    expect(data.reason).toContain("deploy progress unavailable");
  });

  test("floors a fractional request age to a whole millisecond count", async () => {
    // Real filesystem mtimeMs carries sub-millisecond precision; the schema requires an
    // integer. This reproduces the exact zod failure seen once this adapter ran for real
    // (a float reaching the schema, not a test double's round number).
    const result = await adapter({
      now: () => NOW_MS + 0.4,
      lstatImpl: (path) => path === LOCK_PATH
        ? LOCK_STAT
        : { ...LOCK_STAT, mtimeMs: Date.parse("2026-08-12T10:01:00.000Z") + 0.7 },
    }).poll();
    const data = result.panels[0]?.data as { queue?: { oldestAgeMs?: unknown } } | undefined;
    expect(Number.isInteger(data?.queue?.oldestAgeMs)).toBe(true);
  });

  test("reports idle only with available empty queue and unheld lock", async () => {
    const result = await adapter({ readdirImpl: () => [] }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      state: "idle",
      complete: true,
      queue: { depth: 0, oldestAgeMs: null, requests: [] },
      holder: null,
      reason: null,
    });
  });

  test("reports served commit up to date with origin/main as zero commits behind", async () => {
    const sha = "a".repeat(40);
    const result = await adapter({
      readdirImpl: () => [],
      readFileImpl: (path) => {
        if (path === "/proc/locks") return "";
        if (path.endsWith("harness-deployed-sha")) return `${sha}\n`;
        return readFileSync(path, "utf8");
      },
      gitRevImpl: (args) => args.includes("rev-parse") ? sha : null,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      servedSha: sha,
      mainSha: sha,
      commitsBehind: 0,
      mainCommitAt: null,
    });
  });

  test("reports served commit behind origin/main by the counted commits and the tip's commit time", async () => {
    const served = "a".repeat(40);
    const main = "b".repeat(40);
    const result = await adapter({
      readdirImpl: () => [],
      readFileImpl: (path) => {
        if (path === "/proc/locks") return "";
        if (path.endsWith("harness-deployed-sha")) return `${served}\n`;
        return readFileSync(path, "utf8");
      },
      gitRevImpl: (args) => {
        if (args.includes("rev-parse")) return main;
        if (args.includes("rev-list")) return "3";
        if (args.includes("log")) return "2026-08-12T10:00:00+00:00";
        return null;
      },
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      servedSha: served,
      mainSha: main,
      commitsBehind: 3,
      mainCommitAt: "2026-08-12T10:00:00.000Z",
    });
  });

  test("reports never-deployed clone as unknown identity, not a fabricated lag", async () => {
    const result = await adapter({ readdirImpl: () => [] }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      servedSha: null,
      mainSha: null,
      commitsBehind: null,
      mainCommitAt: null,
    });
  });

  test("leaves watcher null when no controller token is configured", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      fetchImpl: (() => { throw new Error("must not fetch without a token"); }) as unknown as typeof fetch,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({ watcher: null });
  });

  test("surfaces the S3 watcher's retry-stop state from the controller's /status", async () => {
    const sha = "a".repeat(40);
    let requestedUrl = "";
    const result = await adapter({
      readdirImpl: () => [],
      controllerToken: "secret",
      controllerUrl: "http://127.0.0.1:8787",
      fetchImpl: (async (input: string) => {
        requestedUrl = String(input);
        return new Response(JSON.stringify({
          deployWatcher: {
            targetSha: sha, attempts: 3, lastStatus: "deploy-clone-dirty",
            lastDetail: "local changes", lastAt: "2026-08-15T05:00:00.000Z", lastOk: false,
            failureClass: "permanent", nextRetryAt: null,
          },
        }), { status: 200 });
      }) as typeof fetch,
    }).poll();
    expect(requestedUrl).toBe("http://127.0.0.1:8787/status");
    expect(result.panels[0]?.data).toMatchObject({
      watcher: {
        targetSha: sha, attempts: 3, lastStatus: "deploy-clone-dirty",
        lastDetail: "local changes", lastAt: "2026-08-15T05:00:00.000Z", lastOk: false,
        failureClass: "permanent", nextRetryAt: null,
      },
    });
  });

  test("fails soft to a null watcher without downgrading the rest of the panel when the controller is unreachable", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      controllerToken: "secret",
      fetchImpl: (async () => { throw new Error("ECONNREFUSED"); }) as unknown as typeof fetch,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      watcher: null,
      servedSha: null,
      reason: null,
    });
  });

  test("fails soft to a null watcher when the controller responds with a non-2xx status", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      controllerToken: "secret",
      fetchImpl: (async () => new Response("unauthorized", { status: 401 })) as unknown as typeof fetch,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({ watcher: null });
  });

  test("fails soft to a null watcher when /status carries no deployWatcher (older controller build)", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      controllerToken: "secret",
      fetchImpl: (async () => new Response(JSON.stringify({}), { status: 200 })) as unknown as typeof fetch,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({ watcher: null });
  });

  test("reports unreadable queue and lock sources as explicit incomplete unknown state", async () => {
    const result = await adapter({
      readdirImpl: () => { throw new Error("EACCES queue"); },
      lstatImpl: () => { throw new Error("EACCES lock"); },
    }).poll();
    const data = result.panels[0]?.data as { state: string; complete: boolean; queue: unknown; holder: unknown; reason: string | null };
    expect(data.state).toBe("unknown");
    expect(data.complete).toBe(false);
    expect(data.queue).toBeNull();
    expect(data.holder).toBeNull();
    expect(data.reason?.includes("deploy queue unavailable")).toBe(true);
    expect(data.reason?.includes("deploy lock unavailable")).toBe(true);
  });

  test("reports malformed kernel lock input as explicit incomplete unknown state", async () => {
    const result = await adapter({ readFileImpl: () => "7: FLOCK ADVISORY WRITE invalid\n" }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      holder: null,
      reason: expect.stringContaining("deploy lock unavailable"),
    });
  });

  test("treats a queued waiter line as a waiter, not as corruption", async () => {
    const result = await adapter({
      readdirImpl: () => ["req-100-7"],
      readFileImpl: locksOnly(`144: -> FLOCK ADVISORY WRITE 3015819 08:01:42 0 EOF\n${HELD_LOCKS}`),
      lstatImpl: () => LOCK_STAT,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({ holder: "pid:4242" });
  });

  test("ignores a shared read lock on the same file", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      readFileImpl: locksOnly("9: FLOCK ADVISORY READ 777 08:01:42 0 EOF\n"),
      lstatImpl: () => LOCK_STAT,
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({ holder: null });
  });

  test("reports malformed lock metadata as explicit incomplete unknown state", async () => {
    const result = await adapter({
      lstatImpl: (path) => path === LOCK_PATH
        ? { ...LOCK_STAT, dev: Number.NaN }
        : lstatSync(path),
    }).poll();
    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      holder: null,
      reason: expect.stringContaining("deploy lock unavailable"),
    });
  });

  test("reports missing lock as unheld while preserving an authoritative queue", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      lstatImpl: (path) => {
        if (path === LOCK_PATH) throw missingPath();
        return lstatSync(path);
      },
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "idle",
      complete: true,
      queue: { depth: 0 },
      holder: null,
      reason: null,
    });
  });

  test("reports proc-locks absence as incomplete unknown rather than missing lock", async () => {
    const result = await adapter({
      readdirImpl: () => [],
      readFileImpl: () => { throw missingPath(); },
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      queue: { depth: 0 },
      holder: null,
      reason: expect.stringContaining("deploy lock unavailable"),
    });
  });

  test("reports lock access failure as incomplete unknown rather than absent", async () => {
    const result = await adapter({
      readdirImpl: () => ["req-100-7"],
      lstatImpl: (path) => {
        if (path === LOCK_PATH) throw new Error("EACCES lock");
        return { ...LOCK_STAT, mtimeMs: Date.parse("2026-08-12T10:01:00.000Z") };
      },
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      queue: { depth: 1 },
      holder: null,
      reason: expect.stringContaining("deploy lock unavailable"),
    });
  });

  test("reports malformed deploy request names instead of authoritative queue depth", async () => {
    const result = await adapter({ readdirImpl: () => ["req-100-example"] }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      queue: null,
      reason: expect.stringContaining("malformed deploy request entry"),
    });
  });

  test("rejects symlink request entries instead of reporting queue depth", async () => {
    const result = await adapter({
      readdirImpl: () => ["req-100-7"],
      lstatImpl: (path) => path === LOCK_PATH ? LOCK_STAT : { ...LOCK_STAT, isFile: () => false },
    }).poll();

    expect(result.panels[0]?.data).toMatchObject({
      state: "unknown",
      complete: false,
      queue: null,
      reason: expect.stringContaining("invalid deploy request entry"),
    });
  });

  test("uses custom id and a positive default interval", () => {
    const result = adapter({ id: "delivery" });
    expect(result.id).toBe("delivery");
    expect(result.interval).toBeGreaterThan(0);
  });
});
