// Unit tests for server/src/localdb/version.ts (PG Task 1). Pure — two numbers
// in, a verdict out.
//
// The verdict this file cares most about is `newer-cluster`. Ten of these tests
// exist so that if someone ever "simplifies" the four verdicts down to
// fresh/match/mismatch, the suite says no.

import { describe, it, expect } from "vitest";
import { checkClusterVersion, parsePostgresMajor } from "../../server/src/localdb/version.js";

describe("parsePostgresMajor", () => {
  it.each<[string, number]>([
    ["16", 16],
    ["16.10", 16],
    ["17.2.1", 17],
    [" 16.10 ", 16],
    ["9", 9],
  ])("%s -> %s", (input, expected) => {
    expect(parsePostgresMajor(input)).toBe(expected);
  });

  it("returns null rather than 0 for anything unparseable", () => {
    // 0 would compare as "older than everything" and silently select the
    // downgrade branch — the one place a wrong answer costs books.
    for (const bad of ["", "   ", "v16", "sixteen", ".16", "-1", "16beta", "0", "0.1"]) {
      expect(parsePostgresMajor(bad)).toBeNull();
    }
  });
});

describe("checkClusterVersion", () => {
  it("reports a first run as fresh, and allows it to proceed", () => {
    const check = checkClusterVersion(16, null);
    expect(check.verdict).toBe("fresh");
    expect(check.canStart).toBe(true);
    expect(check.clusterMajor).toBeNull();
    expect(check.shippedMajor).toBe(16);
  });

  it("reports an exact match, and allows it to proceed", () => {
    const check = checkClusterVersion(16, 16);
    expect(check.verdict).toBe("match");
    expect(check.canStart).toBe(true);
  });

  it("reports an older cluster and REFUSES to start, per the decisions doc", () => {
    // "Until that path exists and is tested, a mismatch must refuse to start
    // with a plain-English explanation."
    const check = checkClusterVersion(17, 16);
    expect(check.verdict).toBe("older-cluster");
    expect(check.canStart).toBe(false);
    expect(check.message).toContain("16");
    expect(check.message).toContain("17");
  });

  it("reports a NEWER cluster as its own verdict, never as 'older' or 'mismatch'", () => {
    const check = checkClusterVersion(16, 17);
    expect(check.verdict).toBe("newer-cluster");
    expect(check.canStart).toBe(false);
  });

  it("keeps the two mismatch verdicts distinct — the same pair, swapped, must differ", () => {
    const older = checkClusterVersion(17, 16);
    const newer = checkClusterVersion(16, 17);
    expect(older.verdict).not.toBe(newer.verdict);
    expect(older.message).not.toBe(newer.message);
  });

  it("tells a newer-cluster user to run the newer version, and that nothing was touched", () => {
    // The wording matters as much as the verdict: this is the screen someone
    // sees after double-clicking last month's installer, and the wrong sentence
    // here is what makes them click through into a fresh initdb.
    const check = checkClusterVersion(16, 17);
    expect(check.message).toContain("NEWER");
    expect(check.message.toLowerCase()).toContain("nothing has been changed");
  });

  it("never says 'upgrade' or 'convert' about a newer cluster — there is no such path", () => {
    const check = checkClusterVersion(16, 18);
    expect(check.message.toLowerCase()).not.toContain("upgrade");
    expect(check.message.toLowerCase()).not.toContain("convert");
  });

  it("refuses across multi-version gaps in both directions", () => {
    expect(checkClusterVersion(20, 14).verdict).toBe("older-cluster");
    expect(checkClusterVersion(14, 20).verdict).toBe("newer-cluster");
    expect(checkClusterVersion(20, 14).canStart).toBe(false);
    expect(checkClusterVersion(14, 20).canStart).toBe(false);
  });

  it("echoes both majors on every verdict, so a support log never has to guess", () => {
    for (const [shipped, cluster] of [[16, null], [16, 16], [17, 16], [16, 17]] as const) {
      const check = checkClusterVersion(shipped, cluster);
      expect(check.shippedMajor).toBe(shipped);
      expect(check.clusterMajor).toBe(cluster);
      expect(check.message.length).toBeGreaterThan(0);
    }
  });

  it("only ever allows a start on fresh or match", () => {
    for (let cluster = 12; cluster <= 20; cluster++) {
      const check = checkClusterVersion(16, cluster);
      expect(check.canStart).toBe(cluster === 16);
    }
  });
});
