import { postgresTest } from "@internal/testcontainers";
import { type PrismaClient } from "@trigger.dev/database";
import { describe, expect, vi } from "vitest";
import { findEnvironmentByApiKey } from "~/models/runtimeEnvironment.server";
import { createTestOrgProjectWithMember, uniqueId } from "./fixtures/environmentVariablesFixtures";

vi.setConfig({ testTimeout: 60_000 });

type EnvOverrides = {
  type: "DEVELOPMENT" | "PREVIEW" | "PRODUCTION";
  orgMemberId?: string | null;
  parentEnvironmentId?: string | null;
  branchName?: string | null;
  isBranchableEnvironment?: boolean;
  archivedAt?: Date | null;
};

async function createEnv(
  prisma: PrismaClient,
  projectId: string,
  organizationId: string,
  overrides: EnvOverrides
) {
  return prisma.runtimeEnvironment.create({
    data: {
      slug: uniqueId("env"),
      apiKey: uniqueId("tr"),
      pkApiKey: uniqueId("pk"),
      shortcode: uniqueId("sc"),
      projectId,
      organizationId,
      type: overrides.type,
      orgMemberId: overrides.orgMemberId ?? null,
      parentEnvironmentId: overrides.parentEnvironmentId ?? null,
      branchName: overrides.branchName ?? null,
      isBranchableEnvironment: overrides.isBranchableEnvironment ?? false,
      archivedAt: overrides.archivedAt ?? null,
    },
  });
}

describe("findEnvironmentByApiKey — DEVELOPMENT branch resolution", () => {
  postgresTest(
    "resolves the full dev auth matrix from the parent's api key",
    async ({ prisma }) => {
      const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma);

      // The existing per-member dev env IS the default branch (no branchName).
      const devRoot = await createEnv(prisma, project.id, organization.id, {
        type: "DEVELOPMENT",
        orgMemberId: orgMember.id,
      });

      const namedBranch = await createEnv(prisma, project.id, organization.id, {
        type: "DEVELOPMENT",
        orgMemberId: orgMember.id,
        parentEnvironmentId: devRoot.id,
        branchName: "my-feature",
      });

      await createEnv(prisma, project.id, organization.id, {
        type: "DEVELOPMENT",
        orgMemberId: orgMember.id,
        parentEnvironmentId: devRoot.id,
        branchName: "archived-feature",
        archivedAt: new Date(),
      });

      // No header → the root dev env (unchanged, day-one behaviour).
      const noHeader = await findEnvironmentByApiKey(devRoot.apiKey, undefined, prisma);
      expect(noHeader?.id).toBe(devRoot.id);

      // "default" sentinel → also the root dev env.
      const defaultHeader = await findEnvironmentByApiKey(devRoot.apiKey, "default", prisma);
      expect(defaultHeader?.id).toBe(devRoot.id);

      // A named branch that exists → the child env...
      const child = await findEnvironmentByApiKey(devRoot.apiKey, "my-feature", prisma);
      expect(child?.id).toBe(namedBranch.id);
      expect(child?.branchName).toBe("my-feature");
      // ...but carrying the PARENT's api key and ownership, not the child's own key.
      expect(child?.apiKey).toBe(devRoot.apiKey);
      expect(child?.orgMemberId).toBe(orgMember.id);

      // A named branch that doesn't exist → null (not a silent fall-through to root).
      const missing = await findEnvironmentByApiKey(devRoot.apiKey, "does-not-exist", prisma);
      expect(missing).toBeNull();

      // An archived branch → null (archivedAt filter on the child include).
      const archived = await findEnvironmentByApiKey(devRoot.apiKey, "archived-feature", prisma);
      expect(archived).toBeNull();
    }
  );

  postgresTest("a branch name is sanitized before lookup", async ({ prisma }) => {
    const { organization, project, orgMember } = await createTestOrgProjectWithMember(prisma);

    const devRoot = await createEnv(prisma, project.id, organization.id, {
      type: "DEVELOPMENT",
      orgMemberId: orgMember.id,
    });
    const namedBranch = await createEnv(prisma, project.id, organization.id, {
      type: "DEVELOPMENT",
      orgMemberId: orgMember.id,
      parentEnvironmentId: devRoot.id,
      branchName: "feature/login",
    });

    // refs/heads/ prefix is stripped to match the stored branch name.
    const resolved = await findEnvironmentByApiKey(
      devRoot.apiKey,
      "refs/heads/feature/login",
      prisma
    );
    expect(resolved?.id).toBe(namedBranch.id);
  });
});

describe("findEnvironmentByApiKey — PREVIEW (regression guard)", () => {
  postgresTest(
    "preview still requires a branch and never resolves the parent",
    async ({ prisma }) => {
      const { organization, project } = await createTestOrgProjectWithMember(prisma);

      const previewParent = await createEnv(prisma, project.id, organization.id, {
        type: "PREVIEW",
        isBranchableEnvironment: true,
      });
      const previewBranch = await createEnv(prisma, project.id, organization.id, {
        type: "PREVIEW",
        parentEnvironmentId: previewParent.id,
        branchName: "pr-123",
      });

      // No header on a preview key → null (preview has no default).
      const noHeader = await findEnvironmentByApiKey(previewParent.apiKey, undefined, prisma);
      expect(noHeader).toBeNull();

      // With a branch → the child, carrying the parent's api key.
      const resolved = await findEnvironmentByApiKey(previewParent.apiKey, "pr-123", prisma);
      expect(resolved?.id).toBe(previewBranch.id);
      expect(resolved?.apiKey).toBe(previewParent.apiKey);
    }
  );
});

describe("findEnvironmentByApiKey — non-branchable", () => {
  postgresTest(
    "a production key ignores the branch header and returns itself",
    async ({ prisma }) => {
      const { organization, project } = await createTestOrgProjectWithMember(prisma);

      const prod = await createEnv(prisma, project.id, organization.id, { type: "PRODUCTION" });

      const resolved = await findEnvironmentByApiKey(prod.apiKey, "some-branch", prisma);
      expect(resolved?.id).toBe(prod.id);
    }
  );

  postgresTest("an unknown api key returns null", async ({ prisma }) => {
    const resolved = await findEnvironmentByApiKey("tr_dev_nonexistent", undefined, prisma);
    expect(resolved).toBeNull();
  });
});
