import { convexTest } from "convex-test";
import { anyApi, makeFunctionReference } from "convex/server";
import { describe, expect, test } from "vitest";

import type { Id } from "./_generated/dataModel";
import schema from "./schema";

declare global {
  interface ImportMeta {
    readonly glob: (pattern: string) => Record<string, () => Promise<unknown>>;
  }
}

const modules = import.meta.glob("./**/*.ts");
const api = anyApi;
const executeFakeAttemptRef = makeFunctionReference<
  "action",
  { attemptId: string; scenario: "success" }
>("workExecution:executeFakeAttempt");
const identity = { tokenIdentifier: "https://convex.test|planner-user" };

describe("Work planning and execution commands", () => {
  test("binds approvals to exact versions and admits a fake Run only from Ready", async () => {
    const t = convexTest({ modules, schema });
    const fixture = await t.withIdentity(identity).run(async (ctx) => {
      const organizationId = await ctx.db.insert("organizations", {
        createdAt: 1,
        createdBy: identity.tokenIdentifier,
        kind: "personal",
        name: "Personal",
      });
      await ctx.db.insert("organizationMembers", {
        createdAt: 1,
        organizationId,
        role: "owner",
        userId: identity.tokenIdentifier,
      });
      const projectId = await ctx.db.insert("projects", {
        createdAt: 1,
        name: "Zopu",
        normalizedSourceUrl: "https://example.com/zopu",
        organizationId,
        repositoryPath: "puter/zopu",
        sourceHost: "example.com",
        sourceUrl: "https://example.com/zopu",
        updatedAt: 1,
      });
      const workId = await ctx.db.insert("works", {
        createdAt: 1,
        objective: "Prove the flow",
        organizationId,
        projectId,
        status: "proposed",
        title: "Executable work",
        updatedAt: 1,
      });
      return { projectId, workId };
    });

    await t
      .withIdentity(identity)
      .mutation(api.workPlanning.requestDefinition, { workId: fixture.workId });
    const definition = await t
      .withIdentity(identity)
      .mutation(api.workPlanning.saveDefinitionProposal, {
        payloadJson: JSON.stringify({
          acceptanceCriteria: ["terminal"],
          affectedUsers: ["user"],
          assumptions: [],
          constraints: [],
          desiredOutcome: "A terminal simulation",
          inScope: ["fake"],
          outOfScope: [],
          problem: "Need a proof",
          questions: [],
          requiredArtifacts: ["events"],
          risk: "low",
        }),
        workId: fixture.workId,
      });
    await t
      .withIdentity(identity)
      .mutation(api.workPlanning.approveDefinition, {
        version: definition.version,
        workId: fixture.workId,
      });
    const design = await t
      .withIdentity(identity)
      .mutation(api.workPlanning.saveDesignProposal, {
        payloadJson: JSON.stringify({
          architectureSummary: "fake",
          callFlowDelta: [],
          concerns: [],
          evidenceRequirements: ["event"],
          fileTreeDelta: [],
          impactMap: { files: [], modules: [], risks: [], summary: "small" },
          invariants: ["terminal"],
          keyInterfaces: [],
          slices: [
            {
              codeBoundaries: ["runtime"],
              dependsOn: [],
              evidenceRequirements: ["event"],
              id: "slice-1",
              objective: "fake",
              observableBehavior: "event",
              reviewRequired: false,
              title: "fake",
              verification: ["assert"],
            },
          ],
          tradeoffs: [],
        }),
        workId: fixture.workId,
      });
    await t.withIdentity(identity).mutation(api.workPlanning.approveDesign, {
      definitionVersion: definition.version,
      designVersion: design.version,
      workId: fixture.workId,
    });
    const started = await t
      .withIdentity(identity)
      .mutation(api.workExecution.startSimulatedExecution, {
        scenario: "success",
        sliceId: "slice-1",
        workId: fixture.workId,
      });
    expect(started.runId).toBeTruthy();
    const state = await t.run(async (ctx) => ctx.db.get(fixture.workId));
    expect(state?.status).toBe("executing");
    expect(state?.definitionApprovalVersion).toBe(definition.version);
    expect(state?.designApprovalVersion).toBe(design.version);
    await t.action(executeFakeAttemptRef, {
      attemptId: String(started.attemptId),
      scenario: "success",
    });
    const completed = await t.run(async (ctx) => ({
      attempt: await ctx.db
        .query("workAttempts")
        .withIndex("by_runId_and_number", (q) => q.eq("runId", started.runId))
        .unique(),
      events: await ctx.db.query("workAttemptEvents").collect(),
      run: await ctx.db.get(started.runId as Id<"workRuns">),
      work: await ctx.db.get(fixture.workId),
    }));
    expect(completed.work?.status).toBe("completed");
    expect(completed.run?.terminalClassification).toBe("Succeeded");
    expect(completed.attempt?.status).toBe("terminal");
    expect(completed.events.length).toBeGreaterThan(0);
  });
});

const validDefinitionPayload = () => ({
  acceptanceCriteria: ["terminal outcome"],
  affectedUsers: ["user"],
  assumptions: [],
  constraints: [],
  desiredOutcome: "A terminal simulation",
  inScope: ["fake"],
  outOfScope: [],
  problem: "Need a proof",
  questions: [],
  requiredArtifacts: ["events"],
  risk: "low",
});

const validDesignPayload = (sliceId = "slice-1") => ({
  architectureSummary: "fake",
  callFlowDelta: [],
  concerns: [],
  evidenceRequirements: ["event"],
  fileTreeDelta: [],
  impactMap: { files: [], modules: [], risks: [], summary: "small" },
  invariants: ["terminal"],
  keyInterfaces: [],
  slices: [
    {
      codeBoundaries: ["runtime"],
      dependsOn: [],
      evidenceRequirements: ["event"],
      id: sliceId,
      objective: "fake",
      observableBehavior: "event",
      reviewRequired: false,
      title: "fake",
      verification: ["assert"],
    },
  ],
  tradeoffs: [],
});

describe("Work planning guards", () => {
  test("persists high-impact questions but blocks approval", async () => {
    const t = convexTest({ modules, schema });
    const { workId } = await t.withIdentity(identity).run(async (ctx) => {
      const organizationId = await ctx.db.insert("organizations", {
        createdAt: 1,
        createdBy: identity.tokenIdentifier,
        kind: "personal",
        name: "Personal",
      });
      await ctx.db.insert("organizationMembers", {
        createdAt: 1,
        organizationId,
        role: "owner",
        userId: identity.tokenIdentifier,
      });
      const projectId = await ctx.db.insert("projects", {
        createdAt: 1,
        name: "Zopu",
        normalizedSourceUrl: "https://example.com/zopu",
        organizationId,
        repositoryPath: "puter/zopu",
        sourceHost: "example.com",
        sourceUrl: "https://example.com/zopu",
        updatedAt: 1,
      });
      const id = await ctx.db.insert("works", {
        createdAt: 1,
        objective: "Resolve a critical question",
        organizationId,
        projectId,
        status: "defining",
        title: "Question gate",
        updatedAt: 1,
      });
      return { workId: id };
    });
    const saved = await t
      .withIdentity(identity)
      .mutation(api.workPlanning.saveDefinitionProposal, {
        payloadJson: JSON.stringify({
          ...validDefinitionPayload(),
          questions: [
            {
              alternatives: ["public", "private"],
              id: "visibility",
              impact: "high",
              prompt: "Should the endpoint be public?",
              status: "open",
            },
          ],
        }),
        workId,
      });
    await expect(
      t.withIdentity(identity).mutation(api.workPlanning.approveDefinition, {
        version: saved.version,
        workId,
      })
    ).rejects.toThrow(/High-impact open questions/u);
    const stored = await t.run(async (ctx) =>
      ctx.db.query("workQuestions").collect()
    );
    expect(stored).toHaveLength(1);
    expect(stored[0]?.status).toBe("open");
  });

  test("keeps prior Design slices as versioned history", async () => {
    const t = convexTest({ modules, schema });
    const { workId } = await t.withIdentity(identity).run(async (ctx) => {
      const organizationId = await ctx.db.insert("organizations", {
        createdAt: 1,
        createdBy: identity.tokenIdentifier,
        kind: "personal",
        name: "Personal",
      });
      await ctx.db.insert("organizationMembers", {
        createdAt: 1,
        organizationId,
        role: "owner",
        userId: identity.tokenIdentifier,
      });
      const projectId = await ctx.db.insert("projects", {
        createdAt: 1,
        name: "Zopu",
        normalizedSourceUrl: "https://example.com/zopu",
        organizationId,
        repositoryPath: "puter/zopu",
        sourceHost: "example.com",
        sourceUrl: "https://example.com/zopu",
        updatedAt: 1,
      });
      const id = await ctx.db.insert("works", {
        createdAt: 1,
        definitionApprovalVersion: 1,
        definitionVersion: 1,
        objective: "Preserve Design history",
        organizationId,
        projectId,
        status: "designing",
        title: "Versioned Design",
        updatedAt: 1,
      });
      return { workId: id };
    });
    await t
      .withIdentity(identity)
      .mutation(api.workPlanning.saveDesignProposal, {
        payloadJson: JSON.stringify(validDesignPayload("slice-v1")),
        workId,
      });
    await t
      .withIdentity(identity)
      .mutation(api.workPlanning.saveDesignProposal, {
        payloadJson: JSON.stringify(validDesignPayload("slice-v2")),
        workId,
      });
    const slices = await t.run(async (ctx) =>
      ctx.db.query("workSlices").collect()
    );
    expect(slices.map((slice) => slice.designVersion).sort()).toEqual([1, 2]);
  });

  test("a Definition cannot be revised while a Run is executing", async () => {
    const t = convexTest({ modules, schema });
    const { workId } = await t.withIdentity(identity).run(async (ctx) => {
      const organizationId = await ctx.db.insert("organizations", {
        createdAt: 1,
        createdBy: identity.tokenIdentifier,
        kind: "personal",
        name: "Personal",
      });
      await ctx.db.insert("organizationMembers", {
        createdAt: 1,
        organizationId,
        role: "owner",
        userId: identity.tokenIdentifier,
      });
      const projectId = await ctx.db.insert("projects", {
        createdAt: 1,
        name: "Zopu",
        normalizedSourceUrl: "https://example.com/zopu",
        organizationId,
        repositoryPath: "puter/zopu",
        sourceHost: "example.com",
        sourceUrl: "https://example.com/zopu",
        updatedAt: 1,
      });
      const id = await ctx.db.insert("works", {
        createdAt: 1,
        definitionApprovalVersion: 1,
        definitionVersion: 1,
        designApprovalVersion: 1,
        designVersion: 1,
        objective: "Prove the guard",
        organizationId,
        projectId,
        status: "executing",
        title: "Guarded work",
        updatedAt: 1,
      });
      return { workId: id };
    });
    await expect(
      t
        .withIdentity(identity)
        .mutation(api.workPlanning.saveDefinitionProposal, {
          payloadJson: JSON.stringify(validDefinitionPayload()),
          workId,
        })
    ).rejects.toThrow(/while a Run is executing/u);
  });

  test("planner questions are persisted as durable Work questions", async () => {
    const t = convexTest({ modules, schema });
    const { workId } = await t.withIdentity(identity).run(async (ctx) => {
      const organizationId = await ctx.db.insert("organizations", {
        createdAt: 1,
        createdBy: identity.tokenIdentifier,
        kind: "personal",
        name: "Personal",
      });
      const projectId = await ctx.db.insert("projects", {
        createdAt: 1,
        name: "Zopu",
        normalizedSourceUrl: "https://example.com/zopu",
        organizationId,
        repositoryPath: "puter/zopu",
        sourceHost: "example.com",
        sourceUrl: "https://example.com/zopu",
        updatedAt: 1,
      });
      const id = await ctx.db.insert("works", {
        createdAt: 1,
        definitionVersion: 1,
        objective: "Prove questions",
        organizationId,
        projectId,
        status: "awaiting-definition-approval",
        title: "Questionable work",
        updatedAt: 1,
      });
      return { workId: id };
    });
    await t.mutation(api.workPlanning.submitQuestion, {
      questionJson: JSON.stringify({
        alternatives: [],
        id: "q1",
        impact: "medium",
        prompt: "Which runtime?",
        status: "open",
      }),
      token: "test-token",
      workId,
    });
    const questions = await t.run(async (ctx) =>
      ctx.db.query("workQuestions").collect()
    );
    expect(questions).toHaveLength(1);
    expect(questions[0]?.questionId).toBe("q1");
    expect(questions[0]?.definitionVersion).toBe(1);
  });
});
