import { PermissionKey, ProjectStatus } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { adminProcedure, managerProcedure, requirePermission, type TRPCContext } from "../trpc.js";

type ProjectLifecycleContext = Pick<TRPCContext, "db" | "dbUser"> & {
  permissions: Set<PermissionKey>;
};

type ProjectLifecycleDependencies = {
  invalidateDashboardCacheInBackground: () => void;
  dispatchProjectWebhookInBackground: (
    db: TRPCContext["db"],
    event: string,
    payload: Record<string, unknown>,
  ) => void;
};

async function deleteProjectCascade(
  ctx: Pick<ProjectLifecycleContext, "db" | "dbUser">,
  project: { id: string; name: string; shortCode: string },
) {
  await ctx.db.$transaction(async (tx) => {
    await tx.assignment.deleteMany({ where: { projectId: project.id } });
    await tx.demandRequirement.deleteMany({ where: { projectId: project.id } });
    await tx.calculationRule.updateMany({
      where: { projectId: project.id },
      data: { projectId: null },
    });
    await tx.project.delete({ where: { id: project.id } });
    await tx.auditLog.create({
      data: {
        entityType: "Project",
        entityId: project.id,
        action: "DELETE",
        ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
        changes: { before: { id: project.id, name: project.name, shortCode: project.shortCode } } as never,
      },
    });
  });
}

async function deleteProjectsCascade(
  ctx: Pick<ProjectLifecycleContext, "db" | "dbUser">,
  projects: Array<{ id: string; name: string; shortCode: string }>,
) {
  await ctx.db.$transaction(async (tx) => {
    const ids = projects.map((project) => project.id);
    await tx.assignment.deleteMany({ where: { projectId: { in: ids } } });
    await tx.demandRequirement.deleteMany({ where: { projectId: { in: ids } } });
    await tx.calculationRule.updateMany({
      where: { projectId: { in: ids } },
      data: { projectId: null },
    });
    await tx.project.deleteMany({ where: { id: { in: ids } } });
    await tx.auditLog.create({
      data: {
        entityType: "Project",
        entityId: ids.join(","),
        action: "DELETE",
        ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
        changes: { before: projects } as never,
      },
    });
  });
}

export function createProjectLifecycleProcedures(
  dependencies: ProjectLifecycleDependencies,
) {
  return {
    updateStatus: managerProcedure
      .input(z.object({ id: z.string(), status: z.nativeEnum(ProjectStatus) }))
      .mutation(async ({ ctx, input }) => {
        requirePermission(ctx, PermissionKey.MANAGE_PROJECTS);
        const result = await ctx.db.project.update({
          where: { id: input.id },
          data: { status: input.status },
        });
        dependencies.invalidateDashboardCacheInBackground();
        dependencies.dispatchProjectWebhookInBackground(ctx.db, "project.status_changed", {
          id: result.id,
          shortCode: result.shortCode,
          name: result.name,
          status: result.status,
        });
        return result;
      }),

    batchUpdateStatus: managerProcedure
      .input(
        z.object({
          ids: z.array(z.string()).min(1).max(100),
          status: z.nativeEnum(ProjectStatus),
        }),
      )
      .mutation(async ({ ctx, input }) => {
        requirePermission(ctx, PermissionKey.MANAGE_PROJECTS);
        const updated = await ctx.db.$transaction(
          input.ids.map((id) =>
            ctx.db.project.update({ where: { id }, data: { status: input.status } }),
          ),
        );

        await ctx.db.auditLog.create({
          data: {
            entityType: "Project",
            entityId: input.ids.join(","),
            action: "UPDATE",
            changes: { after: { status: input.status, ids: input.ids } },
          },
        });

        dependencies.invalidateDashboardCacheInBackground();
        return { count: updated.length };
      }),

    delete: adminProcedure
      .input(z.object({ id: z.string() }))
      .mutation(async ({ ctx, input }) => {
        const project = await ctx.db.project.findUnique({
          where: { id: input.id },
          select: { id: true, name: true, shortCode: true },
        });
        if (!project) {
          throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
        }

        await deleteProjectCascade(ctx, project);
        dependencies.invalidateDashboardCacheInBackground();
        return { id: input.id, name: project.name };
      }),

    batchDelete: adminProcedure
      .input(
        z.object({
          ids: z.array(z.string()).min(1).max(50),
        }),
      )
      .mutation(async ({ ctx, input }) => {
        const projects = await ctx.db.project.findMany({
          where: { id: { in: input.ids } },
          select: { id: true, name: true, shortCode: true },
        });

        if (projects.length === 0) {
          throw new TRPCError({ code: "NOT_FOUND", message: "No projects found" });
        }

        await deleteProjectsCascade(ctx, projects);
        dependencies.invalidateDashboardCacheInBackground();
        return { count: projects.length };
      }),
  };
}
