import { afterEach, describe, expect, test } from "bun:test";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DeliveryFeatureActivator } from "./activation";
import { definitionDigest, type FeatureDefinition, type LoadedFeatureDefinition, type RuntimeFeatureRegistry } from "./definitions";
import { evaluateDeliveryReadiness } from "./readiness";
import { ControllerStore, DeliveryEvidenceAdmissionService, StaleRevisionError, type DeliveryFeatureReceiptRecord } from "../store";
import { TransitionEngine } from "../transitions";
import { startAdmissionReconcileLoop } from "../admission-loop";

const sha = "a".repeat(40);
const tree = "b".repeat(40);
const artifact = "c".repeat(64);
const featureId = "delivery-test";
const targetId = "controller";
const definition: FeatureDefinition = {
  id: featureId, changeClass: "behavior-change", owner: "owner", fallback: "old behavior", defaultState: "OFF",
  readinessChecks: ["accept", "smoke"], observability: ["ledger"], reviewDate: "2026-09-01", cleanupTask: "#5", rollbackWindowDays: 7,
  removalCondition: "remove after rollback window", introduced: { task: "#5", sha }, activationPolicy: "auto-after-receipts",
};
const loaded: LoadedFeatureDefinition = { definition, digest: definitionDigest(definition), path: "test.json" };
const registry: RuntimeFeatureRegistry = { available: true, definitions: new Map([[featureId, loaded]]), resolve: (id) => id === featureId ? { available: true, definition: loaded } : { available: false, reason: "missing-definition", detail: { code: "not-found" } } };
const dirs: string[] = [];
afterEach(() => { for (const dir of dirs.splice(0)) rmSync(dir, { recursive: true, force: true }); });
function makeStore(options: ConstructorParameters<typeof ControllerStore>[1] = {}): { store: ControllerStore; path: string } {
  const dir = mkdtempSync(join(tmpdir(), "delivery-activation-")); dirs.push(dir);
  const path = join(dir, "controller.db");
  return { store: new ControllerStore(path, options), path };
}
function receipt(kind: "ACCEPTANCE" | "SMOKE", overrides: Partial<DeliveryFeatureReceiptRecord> = {}): DeliveryFeatureReceiptRecord {
  return {
    receiptId: kind.toLowerCase(), featureId, definitionDigest: loaded.digest, kind,
    checkId: kind === "ACCEPTANCE" ? "accept" : "smoke", candidateSha: sha, candidateTree: tree,
    deploymentId: "deploy-1", targetId, deployedSha: sha, deployedTree: tree, artifactDigest: artifact,
    result: "PASSED", observedAt: "2026-08-11T00:00:00.000Z", evidenceReference: `${kind.toLowerCase()}-evidence`, ...overrides,
  };
}
function seed(store: ControllerStore): void {
  new DeliveryEvidenceAdmissionService(store).admit(
    { deploymentId: "deploy-1", targetId, deployedSha: sha, deployedTree: tree, artifactDigest: artifact, status: "ACTIVE", observedAt: "2026-08-11T00:00:00.000Z", evidenceReference: "deploy-evidence" },
    [receipt("ACCEPTANCE"), receipt("SMOKE")],
    "test-attestation",
  );
}
function activator(store: ControllerStore, now = () => Date.parse("2026-08-11T00:00:00.000Z")): DeliveryFeatureActivator {
  return new DeliveryFeatureActivator(store, registry, () => targetId, now);
}

describe("delivery readiness and activation", () => {
  test("refuses every wrong-candidate readiness receipt", () => {
    const { store } = makeStore(); seed(store);
    const deployment = store.getActiveDeliveryDeployment(targetId)!;
    const receipts = [receipt("ACCEPTANCE"), receipt("SMOKE", { candidateSha: "d".repeat(40) })];
    expect(evaluateDeliveryReadiness({ available: true, definition: loaded }, { state: null, deployment, receipts })).toEqual({ admitted: false, reason: "missing-exact-smoke-receipt" });
    store.close();
  });

  test("replays deterministic refusal without creating another journal", () => {
    const { store } = makeStore();
    const delivery = activator(store);
    const first = delivery.reconcile(featureId, "refused", 0);
    expect(first).toEqual({ activated: false, reason: "missing-active-deployment" });
    seed(store);
    expect(delivery.reconcile(featureId, "refused", 0)).toEqual(first);
    expect(store.listPendingJournals()).toEqual([]);
    store.close();
  });

  test("resumes same-process retry after lost response without duplicate journal", () => {
    const { store } = makeStore({ crashAfterJournal: true }); seed(store);
    const delivery = activator(store);
    expect(() => delivery.reconcile(featureId, "same-process", 0)).toThrow("simulated crash after journal");
    expect(delivery.reconcile(featureId, "same-process", 0)).toMatchObject({ activated: true, state: "INTERNAL" });
    expect(store.listPendingJournals()).toEqual([]);
    store.close();
  });

  test("orders generic recovery before delivery recovery without cancelling delivery intent", async () => {
    const { store, path } = makeStore({ crashAfterJournal: true }); seed(store);
    expect(() => activator(store).reconcile(featureId, "resume", 0)).toThrow("simulated crash after journal");
    store.close();
    const reopened = new ControllerStore(path);
    expect(await new TransitionEngine(reopened).resumePending()).toEqual([]);
    expect(reopened.listPendingJournals()).toHaveLength(1);
    expect(activator(reopened).resumePending()).toEqual([{ activated: true, state: "INTERNAL", deploymentId: "deploy-1", receiptIds: { acceptance: "acceptance", smoke: "smoke" } }]);
    expect(reopened.getDeliveryFeatureState(featureId)).toMatchObject({ state: "INTERNAL", deployedSha: sha });
    expect(activator(reopened).reconcile(featureId, "resume", 0)).toEqual({ activated: true, state: "INTERNAL", deploymentId: "deploy-1", receiptIds: { acceptance: "acceptance", smoke: "smoke" } });
    reopened.close();
  });

  test("owns authorized INTERNAL through RETIRED lifecycle and OFF rollback", () => {
    const { store } = makeStore(); seed(store);
    const delivery = activator(store);
    expect(delivery.transition(featureId, "INTERNAL", "internal", 0)).toMatchObject({ activated: true, state: "INTERNAL" });
    expect(delivery.transition(featureId, "CANARY", "canary", 1)).toMatchObject({ activated: true, state: "CANARY" });
    expect(delivery.transition(featureId, "OFF", "rollback", 2)).toEqual({ activated: true, state: "OFF" });
    expect(delivery.transition(featureId, "INTERNAL", "internal-2", 3)).toMatchObject({ activated: true, state: "INTERNAL" });
    expect(delivery.transition(featureId, "CANARY", "canary-2", 4)).toMatchObject({ activated: true, state: "CANARY" });
    expect(delivery.transition(featureId, "ON", "on", 5)).toMatchObject({ activated: true, state: "ON" });
    expect(delivery.transition(featureId, "RETIRED", "retired", 6)).toMatchObject({ activated: true, state: "RETIRED" });
    store.close();
  });

  test("persists illegal and malformed recovery refusals without retargeting", () => {
    const { store } = makeStore(); seed(store);
    const delivery = activator(store);
    expect(delivery.transition(featureId, "INTERNAL", "internal", 0)).toMatchObject({ activated: true });
    const repeated = delivery.transition(featureId, "INTERNAL", "repeat", 1);
    expect(repeated).toEqual({ activated: false, reason: "illegal-state-transition" });
    expect(delivery.transition(featureId, "INTERNAL", "repeat", 1)).toEqual(repeated);
    store.journalIntent({ verb: "delivery-feature-reconcile", idempotencyKey: "malformed", args: { featureId, state: "UNKNOWN", targetId }, expectedRevision: 1 });
    store.journalIntent({ verb: "delivery-feature-reconcile", idempotencyKey: "null-target", args: { featureId, state: "CANARY", targetId: null }, expectedRevision: 1 });
    const recovery = new DeliveryFeatureActivator(store, registry, () => targetId);
    expect(recovery.resumePending()).toEqual([{ activated: false, reason: "invalid-persisted-state" }, { activated: false, reason: "missing-active-deployment" }]);
    expect(store.listPendingJournals()).toEqual([]);
    store.close();
  });

  test("permits OFF rollback and retirement after review expiry", () => {
    const { store } = makeStore(); seed(store);
    const beforeExpiry = activator(store);
    beforeExpiry.transition(featureId, "INTERNAL", "internal", 0);
    beforeExpiry.transition(featureId, "CANARY", "canary", 1);
    beforeExpiry.transition(featureId, "ON", "on", 2);
    const expired = activator(store, () => Date.parse("2026-09-02T00:00:00.000Z"));
    expect(expired.transition(featureId, "RETIRED", "retire", 3)).toMatchObject({ activated: true, state: "RETIRED" });
    store.close();
  });

  test("retires expired ON state and removes state and evidence after rollback window", async () => {
    let now = Date.parse("2026-08-11T00:00:00.000Z");
    const { store } = makeStore({ now: () => now }); seed(store);
    const delivery = activator(store, () => now);
    delivery.transition(featureId, "INTERNAL", "internal", 0);
    delivery.transition(featureId, "CANARY", "canary", 1);
    delivery.transition(featureId, "ON", "on", 2);
    const loop = startAdmissionReconcileLoop({ store, engine: new TransitionEngine(store), reconcileDeliveryLifecycle: () => delivery.reconcileExpired(), intervalMs: 3_600_000, now: () => now, log: () => {} });
    await loop.firstPass;
    now = Date.parse("2026-09-02T00:00:00.000Z");
    await loop.tick();
    expect(store.getDeliveryFeatureState(featureId)?.state).toBe("RETIRED");
    now += 8 * 86_400_000;
    await loop.tick();
    expect(store.getDeliveryFeatureState(featureId)).toBeNull();
    expect(store.listDeliveryFeatureReceipts(featureId)).toEqual([]);
    loop.stop();
    store.close();
  });

  test("rolls back activation atomically on evidence drift", () => {
    const { store } = makeStore(); seed(store);
    const original = store.listDeliveryFeatureReceipts.bind(store);
    let reads = 0;
    store.listDeliveryFeatureReceipts = (id) => ++reads === 1 ? original(id) : original(id).filter((item) => item.kind !== "SMOKE");
    expect(() => activator(store).reconcile(featureId, "drift", 0)).toThrow("delivery evidence drift");
    expect(store.getDeliveryFeatureState(featureId)).toBeNull();
    expect(store.listPendingJournals()).toHaveLength(1);
    store.close();
  });

  test("rolls back on CAS conflict before activation", () => {
    const { store } = makeStore(); seed(store);
    const original = store.getDeliveryFeatureState.bind(store);
    let reads = 0;
    store.getDeliveryFeatureState = (id) => {
      if (++reads === 2) {
        const journalId = store.journalIntent({ verb: "admission-reconcile", idempotencyKey: "concurrent", args: {}, expectedRevision: 0 });
        store.commitTransition({ journalId, idempotencyKey: "concurrent", result: {}, apply: () => {} });
      }
      return original(id);
    };
    expect(() => activator(store).reconcile(featureId, "cas", 0)).toThrow(StaleRevisionError);
    expect(store.getDeliveryFeatureState(featureId)).toBeNull();
    store.close();
  });

  test("fails closed after definition review date", () => {
    const { store } = makeStore(); seed(store);
    expect(activator(store, () => Date.parse("2026-09-02T00:00:00.000Z")).reconcile(featureId, "expired", 0)).toEqual({ activated: false, reason: "expired-definition" });
    store.close();
  });
});
