import {
  deleteDemandRequirement,
  fillOpenDemand,
  updateDemandRequirement,
} from "@capakraken/application";
import {
  CreateDemandRequirementSchema,
  FillDemandRequirementSchema,
  FillOpenDemandByAllocationSchema,
  PermissionKey,
  UpdateDemandRequirementSchema,
} from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import {
  emitAllocationCreated,
  emitAllocationDeleted,
  emitAllocationUpdated,
} from "../sse/event-bus.js";
import {
  checkBudgetThresholdsInBackground,
  createDemandRequirementWithEffects,
  dispatchAllocationWebhookInBackground,
  fillDemandRequirementWithEffects,
  invalidateDashboardCacheInBackground,
} from "./allocation-effects.js";
import { DEMAND_INCLUDE } from "./allocation-shared.js";
import {
  buildCreateDemandRequirementInput,
  getDemandRequirementByIdOrThrow,
} from "./allocation-support.js";
import {
  managerProcedure,
  requirePermission,
} from "../trpc.js";

export const allocationDemandProcedures = {
  createDemandRequirement: managerProcedure
    .input(CreateDemandRequirementSchema)
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      return createDemandRequirementWithEffects(ctx.db, input);
    }),

  createDemand: managerProcedure
    .input(z.object({
      projectId: z.string(),
      role: z.string().optional(),
      roleId: z.string().optional(),
      headcount: z.number().int().positive().default(1),
      hoursPerDay: z.number().min(0.5).max(24),
      startDate: z.coerce.date(),
      endDate: z.coerce.date(),
      budgetCents: z.number().int().min(0).optional(),
      metadata: z.record(z.string(), z.unknown()).optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      return createDemandRequirementWithEffects(
        ctx.db,
        buildCreateDemandRequirementInput(input),
      );
    }),

  updateDemandRequirement: managerProcedure
    .input(z.object({ id: z.string(), data: UpdateDemandRequirementSchema }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);

      const updated = await ctx.db.$transaction(async (tx) => {
        return updateDemandRequirement(
          tx as unknown as Parameters<typeof updateDemandRequirement>[0],
          input.id,
          input.data,
        );
      });

      emitAllocationUpdated({
        id: updated.id,
        projectId: updated.projectId,
        resourceId: null,
      });
      invalidateDashboardCacheInBackground();
      checkBudgetThresholdsInBackground(ctx.db, updated.projectId);

      return updated;
    }),

  deleteDemandRequirement: managerProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);

      const existing = await findUniqueOrThrow(
        ctx.db.demandRequirement.findUnique({
          where: { id: input.id },
          include: DEMAND_INCLUDE,
        }),
        "Demand requirement",
      );

      await ctx.db.$transaction(async (tx) => {
        await deleteDemandRequirement(
          tx as unknown as Parameters<typeof deleteDemandRequirement>[0],
          input.id,
        );

        await tx.auditLog.create({
          data: {
            entityType: "DemandRequirement",
            entityId: input.id,
            action: "DELETE",
            changes: { before: existing } as unknown as import("@capakraken/db").Prisma.InputJsonValue,
          },
        });
      });

      emitAllocationDeleted(existing.id, existing.projectId);
      dispatchAllocationWebhookInBackground(ctx.db, "allocation.deleted", {
        id: existing.id,
        projectId: existing.projectId,
      });
      invalidateDashboardCacheInBackground();
      checkBudgetThresholdsInBackground(ctx.db, existing.projectId);

      return { success: true };
    }),

  fillDemandRequirement: managerProcedure
    .input(FillDemandRequirementSchema)
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      return fillDemandRequirementWithEffects(ctx.db, input);
    }),

  assignResourceToDemand: managerProcedure
    .input(z.object({
      demandRequirementId: z.string(),
      resourceId: z.string(),
    }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const result = await fillDemandRequirementWithEffects(ctx.db, input);
      const demandRequirement = await getDemandRequirementByIdOrThrow(
        ctx.db,
        input.demandRequirementId,
      );

      return {
        ...result,
        demandRequirement,
      };
    }),

  fillOpenDemandByAllocation: managerProcedure
    .input(FillOpenDemandByAllocationSchema)
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);

      const result = await fillOpenDemand(ctx.db, input);

      emitAllocationCreated(result.createdAllocation);

      if (result.updatedAllocation) {
        emitAllocationUpdated(result.updatedAllocation);
      }
      invalidateDashboardCacheInBackground();
      checkBudgetThresholdsInBackground(ctx.db, result.createdAllocation.projectId as string);

      return result;
    }),
};
