import {
  createAssignment,
  updateAssignment,
} from "@capakraken/application";
import {
  AllocationStatus,
  CreateAllocationSchema,
  CreateAssignmentSchema,
  PermissionKey,
  UpdateAllocationSchema,
  UpdateAssignmentSchema,
} from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import {
  publishAllocationCreated,
  publishAllocationDeleted,
  publishAllocationUpdated,
  publishBatchAllocationDeletes,
  publishBatchAllocationStatusUpdates,
} from "./allocation-assignment-effects.js";
import {
  batchDeleteAllocationsWithAudit,
  batchUpdateAllocationStatusWithAudit,
  createAllocationReadModelEntry,
  deleteAllocationWithAudit,
  deleteAssignmentWithAudit,
  ensureAssignmentRecord,
  updateAllocationWithAudit,
} from "./allocation-assignment-mutations.js";
import {
  managerProcedure,
  requirePermission,
} from "../trpc.js";

export const allocationAssignmentProcedures = {
  create: managerProcedure
    .input(CreateAllocationSchema)
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const allocation = await ctx.db.$transaction(async (tx) =>
        createAllocationReadModelEntry(
          tx as Parameters<typeof createAssignment>[0],
          input,
        ));

      publishAllocationCreated(ctx.db, {
        id: allocation.id,
        projectId: allocation.projectId,
        resourceId: allocation.resourceId,
      }, { dispatchWebhook: true });
      return allocation;
    }),

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

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

      publishAllocationCreated(ctx.db, {
        id: assignment.id,
        projectId: assignment.projectId,
        resourceId: assignment.resourceId,
      });

      return assignment;
    }),

  ensureAssignment: managerProcedure
    .input(z.object({
      resourceId: z.string(),
      projectId: z.string(),
      startDate: z.coerce.date(),
      endDate: z.coerce.date(),
      hoursPerDay: z.number().min(0.5).max(24),
      role: z.string().optional(),
    }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const result = await ensureAssignmentRecord(ctx.db, {
        resourceId: input.resourceId,
        projectId: input.projectId,
        startDate: input.startDate,
        endDate: input.endDate,
        hoursPerDay: input.hoursPerDay,
        ...(input.role !== undefined ? { role: input.role } : {}),
      });

      if (result.action === "reactivated") {
        publishAllocationUpdated(ctx.db, {
          id: result.assignment.id,
          projectId: result.assignment.projectId,
          resourceId: result.assignment.resourceId,
        }, { dispatchWebhook: true });

        return result;
      }

      publishAllocationCreated(ctx.db, {
        id: result.assignment.id,
        projectId: result.assignment.projectId,
        resourceId: result.assignment.resourceId,
      });

      return result;
    }),

  updateAssignment: managerProcedure
    .input(z.object({ id: z.string(), data: UpdateAssignmentSchema }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const existing = await findUniqueOrThrow(
        ctx.db.assignment.findUnique({
          where: { id: input.id },
          select: { resourceId: true },
        }),
        "Assignment",
      );

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

      publishAllocationUpdated(ctx.db, {
        id: updated.id,
        projectId: updated.projectId,
        resourceId: updated.resourceId,
        resourceIds: [existing.resourceId, updated.resourceId],
      }, { dispatchWebhook: true });

      return updated;
    }),

  update: managerProcedure
    .input(z.object({ id: z.string(), data: UpdateAllocationSchema }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const { existing, updated } = await updateAllocationWithAudit(ctx.db, input.id, input.data);

      const affectedResourceIds = [existing.entry.resourceId, updated.resourceId].filter(
        (resourceId): resourceId is string => Boolean(resourceId),
      );

      publishAllocationUpdated(ctx.db, {
        id: updated.id,
        projectId: updated.projectId,
        resourceId: updated.resourceId,
        resourceIds: affectedResourceIds,
      });

      return updated;
    }),

  deleteAssignment: managerProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const existing = await deleteAssignmentWithAudit(ctx.db, input.id);

      publishAllocationDeleted(ctx.db, {
        id: existing.id,
        projectId: existing.projectId,
        resourceId: existing.resourceId,
      });

      return { success: true };
    }),

  delete: managerProcedure
    .input(z.object({ id: z.string() }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const existing = await deleteAllocationWithAudit(ctx.db, input.id);

      publishAllocationDeleted(ctx.db, {
        id: existing.entry.id,
        projectId: existing.projectId,
        resourceId: existing.entry.resourceId,
      });

      return { success: true };
    }),

  batchDelete: managerProcedure
    .input(z.object({ ids: z.array(z.string()).min(1).max(100) }))
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const existing = await batchDeleteAllocationsWithAudit(ctx.db, input.ids);

      publishBatchAllocationDeletes(ctx.db, existing.map((allocation) => ({
        id: allocation.entry.id,
        projectId: allocation.projectId,
        resourceId: allocation.entry.resourceId,
      })));

      return { count: existing.length };
    }),

  batchUpdateStatus: managerProcedure
    .input(
      z.object({
        ids: z.array(z.string()).min(1).max(100),
        status: z.nativeEnum(AllocationStatus),
      }),
    )
    .mutation(async ({ ctx, input }) => {
      requirePermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const updated = await batchUpdateAllocationStatusWithAudit(ctx.db, input);

      publishBatchAllocationStatusUpdates(ctx.db, updated.map((allocation) => ({
        id: allocation.id,
        projectId: allocation.projectId,
        resourceId: allocation.resourceId,
      })));

      return { count: updated.length };
    }),
};
