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

import { internal } from "./_generated/api";
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 ID_A = "https://convex.test|project-request-a";
const ID_B = "https://convex.test|project-request-b";
const identityA = { tokenIdentifier: ID_A };
const identityB = { tokenIdentifier: ID_B };

const createProject = async (
  t: TestConvex<typeof schema>
): Promise<Id<"projects">> => {
  await t
    .withIdentity(identityA)
    .mutation(api.organizations.ensurePersonalOrganization, {});
  const outcome = await t
    .withIdentity(identityA)
    .mutation(internal.projects.persistPublicGitImport, {
      userId: ID_A,
      source: {
        host: "github.com",
        normalizedUrl: "https://github.com/test/project-request",
        projectName: "project-request",
        repositoryPath: "test/project-request",
        url: "https://github.com/test/project-request",
      },
      remote: {
        defaultBranch: "main",
        documents: [
          {
            content: "# Project Request\n",
            kind: "readme" as const,
            path: "README.md",
          },
        ],
        warnings: [],
      },
    });
  return outcome.id as unknown as Id<"projects">;
};

describe("project issue request smoke", () => {
  test("project Signal becomes a queued project issue with durable evidence", async () => {
    const t = convexTest({ schema, modules });
    const projectId = await createProject(t);
    const organization = await t
      .withIdentity(identityA)
      .mutation(api.organizations.ensurePersonalOrganization, {});
    const begun = await t
      .withIdentity(identityA)
      .mutation(api.conversationMessages.beginUserMessage, {
        clientRequestId: "project-request-1",
        organizationId: organization._id,
        rawText: "Staging deploys fail during DNS resolution.",
      });
    await t
      .withIdentity(identityA)
      .mutation(api.conversationMessages.markAdmitted, {
        clientRequestId: "project-request-1",
        organizationId: organization._id,
        submissionId: "submission-project-request-1",
      });
    const signal = await t
      .withIdentity(identityA)
      .mutation(api.signals.createFromMessages, {
        conversationId: organization._id,
        messageIds: [begun.messageId],
        organizationId: organization._id,
        problemStatement: {
          constraints: ["Do not change production"],
          desiredOutcome: "Staging deploys successfully",
          summary: "The staging deploy fails during DNS resolution.",
          title: "Fix the staging deploy DNS failure",
        },
        processedBy: {
          agentInstanceId: String(organization._id),
          agentName: "zopu",
        },
        projectId,
      });

    const created = await t
      .withIdentity(identityA)
      .mutation(api.projectIssues.createFromSignal, {
        signalId: signal.signalId,
      });
    const issue = await t.run(async (ctx) => ctx.db.get(created.issueId));

    expect(issue).toMatchObject({
      body: expect.stringContaining("Source Signal"),
      projectId,
      status: "open",
      title: "Fix the staging deploy DNS failure",
    });

    const status = await t
      .withIdentity(identityA)
      .mutation(api.projectIssues.begin, { issueId: created.issueId });
    expect(status).toBe("queued");

    const events = await t.run(async (ctx) =>
      ctx.db
        .query("projectEvents")
        .withIndex("by_project_and_createdAt", (q) =>
          q.eq("projectId", projectId)
        )
        .collect()
    );
    expect(events.map((event) => event.kind)).toEqual([
      "project.connected",
      "issue.created",
      "issue.queued",
    ]);
  });

  test("a different organization cannot consume the project Signal", async () => {
    const t = convexTest({ schema, modules });
    const projectId = await createProject(t);
    await t
      .withIdentity(identityB)
      .mutation(api.organizations.ensurePersonalOrganization, {});
    const organization = await t
      .withIdentity(identityA)
      .mutation(api.organizations.ensurePersonalOrganization, {});
    const begun = await t
      .withIdentity(identityA)
      .mutation(api.conversationMessages.beginUserMessage, {
        clientRequestId: "project-request-cross-org",
        organizationId: organization._id,
        rawText: "Private project request",
      });
    await t
      .withIdentity(identityA)
      .mutation(api.conversationMessages.markAdmitted, {
        clientRequestId: "project-request-cross-org",
        organizationId: organization._id,
        submissionId: "submission-project-request-cross-org",
      });
    const signal = await t
      .withIdentity(identityA)
      .mutation(api.signals.createFromMessages, {
        conversationId: organization._id,
        messageIds: [begun.messageId],
        organizationId: organization._id,
        problemStatement: {
          constraints: [],
          desiredOutcome: "Keep the request private",
          summary: "This request belongs to the first organization.",
          title: "Private project request",
        },
        processedBy: {
          agentInstanceId: String(organization._id),
          agentName: "zopu",
        },
        projectId,
      });

    await expect(
      t.withIdentity(identityB).mutation(api.projectIssues.createFromSignal, {
        signalId: signal.signalId,
      })
    ).rejects.toThrow(/membership required/u);
  });
});
