import type { Prisma, Project } from "@trigger.dev/database";
import { customAlphabet, nanoid } from "nanoid";
import slug from "slug";
import { $replica, prisma } from "~/db.server";
import { projectCreated } from "~/services/projectCreated.server";
import { type Organization, createEnvironment } from "./organization.server";
export type { Project } from "@trigger.dev/database";

const externalRefGenerator = customAlphabet("abcdefghijklmnopqrstuvwxyz", 20);

type Options = {
  organizationSlug: string;
  name: string;
  userId: string;
  version: "v2" | "v3";
  onboardingData?: Prisma.InputJsonValue;
};

export class ExceededProjectLimitError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "ExceededProjectLimitError";
  }
}

export async function createProject(
  { organizationSlug, name, userId, version, onboardingData }: Options,
  attemptCount = 0
): Promise<Project & { organization: Organization }> {
  //check the user has permissions to do this
  const organization = await prisma.organization.findFirst({
    select: {
      id: true,
      slug: true,
      isActivated: true,
      maximumConcurrencyLimit: true,
      maximumProjectCount: true,
    },
    where: {
      slug: organizationSlug,
      members: { some: { userId } },
    },
  });

  if (!organization) {
    throw new Error(
      `User ${userId} does not have permission to create a project in organization ${organizationSlug}`
    );
  }

  if (version === "v3") {
    if (!organization.isActivated) {
      throw new Error(`Organization can't create v3 projects.`);
    }
  }

  const projectCount = await prisma.project.count({
    where: {
      organizationId: organization.id,
      deletedAt: null,
    },
  });

  if (projectCount >= organization.maximumProjectCount) {
    throw new ExceededProjectLimitError(
      `This organization has reached the maximum number of projects (${organization.maximumProjectCount}).`
    );
  }

  //ensure the slug is globally unique
  const uniqueProjectSlug = `${slug(name)}-${nanoid(4)}`;
  const projectWithSameSlug = await prisma.project.findFirst({
    where: { slug: uniqueProjectSlug },
  });

  if (attemptCount > 100) {
    throw new Error(`Unable to create project with slug ${uniqueProjectSlug} after 100 attempts`);
  }

  if (projectWithSameSlug) {
    return createProject(
      {
        organizationSlug,
        name,
        userId,
        version,
        onboardingData,
      },
      attemptCount + 1
    );
  }

  const project = await prisma.project.create({
    data: {
      name,
      slug: uniqueProjectSlug,
      organization: {
        connect: {
          slug: organizationSlug,
        },
      },
      externalRef: `proj_${externalRefGenerator()}`,
      version: version === "v3" ? "V3" : "V2",
      onboardingData,
    },
    include: {
      organization: {
        include: {
          members: true,
        },
      },
    },
  });

  // Create the dev and prod environments
  await createEnvironment({
    organization,
    project,
    type: "PRODUCTION",
    isBranchableEnvironment: false,
  });

  for (const member of project.organization.members) {
    await createEnvironment({
      organization,
      project,
      type: "DEVELOPMENT",
      // We set this true but no backfill (yet!?) so never used
      // for dev environments
      isBranchableEnvironment: true,
      member,
    });
  }

  await projectCreated(organization, project);

  return project;
}

export async function findProjectBySlug(orgSlug: string, projectSlug: string, userId: string) {
  // Find the project scoped to the organization, making sure the user belongs to that org
  return await $replica.project.findFirst({
    where: {
      slug: projectSlug,
      organization: {
        slug: orgSlug,
        members: { some: { userId } },
      },
    },
  });
}

export async function findProjectByRef(externalRef: string, userId: string) {
  // Find the project scoped to the organization, making sure the user belongs to that org
  return await $replica.project.findFirst({
    where: {
      externalRef,
      organization: {
        members: { some: { userId } },
      },
    },
  });
}
