import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { approvalRequestLifecycle } from "../db/approvalRequest.lifecycle.generated";
import { approvalStepLifecycle } from "../db/approvalStep.lifecycle.generated";
import { approvalStepAssigneeLifecycle } from "../db/approvalStepAssignee.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidStatusTransitionError,
  NotRequesterError,
  RequestNotFoundError,
} from "../lib/errors.generated";

export interface ResubmitApprovalRequestInput {
  approvalRequestId: string;
  comment?: string;
}

/**
 * Function: resubmitApprovalRequest
 *
 * The requester's reply to a send-back. Returns a REVISION_REQUESTED request to
 * PENDING and restarts approval from the first step: every ApprovalStep is reset
 * to PENDING, every APPROVED ApprovalStepAssignee is reset to PENDING, and the
 * lowest-stepOrder step is re-activated to IN_PROGRESS. The restart reuses the
 * already-frozen assignee set — it does not re-expand roles or re-read the
 * source policy — and never deletes prior ApprovalDecision rows, so the full
 * send-back/resubmit history is preserved.
 */
export async function run(
  db: Transaction,
  input: ResubmitApprovalRequestInput,
  ctx: CommandContext,
) {
  const request = await db
    .selectFrom("ApprovalRequest")
    .selectAll()
    .where("id", "=", input.approvalRequestId)
    .forUpdate()
    .executeTakeFirst();
  if (!request) return err(new RequestNotFoundError(input.approvalRequestId));

  const nextStatus = approvalRequestLifecycle.tryTransition(request.status, "resubmit");
  if (!nextStatus) return err(new InvalidStatusTransitionError(input.approvalRequestId));

  if (ctx.actorId !== request.requesterId) {
    return err(new NotRequesterError(input.approvalRequestId));
  }

  const now = new Date();
  await db
    .insertInto("ApprovalDecision")
    .values({
      approvalRequestId: request.id,
      decision: "RESUBMIT",
      decidedByUserId: ctx.actorId,
      comment: input.comment,
      decidedAt: now,
    })
    .execute();

  const steps = await db
    .selectFrom("ApprovalStep")
    .select(["id", "stepOrder"])
    .where("approvalRequestId", "=", request.id)
    .orderBy("stepOrder", "asc")
    .execute();
  const stepIds = steps.map((s) => s.id);

  await db
    .updateTable("ApprovalStep")
    .set({ status: approvalStepLifecycle.transitions.reset.to, resolvedAt: null })
    .where("approvalRequestId", "=", request.id)
    .execute();

  // DELEGATED rows stay: reviving the delegator would seat them alongside the delegatee's row.
  if (stepIds.length > 0) {
    await db
      .updateTable("ApprovalStepAssignee")
      .set({ status: approvalStepAssigneeLifecycle.transitions.reset.to, resolvedAt: null })
      .where("approvalStepId", "in", stepIds)
      .where("status", "in", approvalStepAssigneeLifecycle.transitions.reset.from)
      .execute();
  }

  const firstStep = steps[0];
  if (firstStep) {
    await db
      .updateTable("ApprovalStep")
      .set({ status: approvalStepLifecycle.transitions.activate.to })
      .where("id", "=", firstStep.id)
      .execute();
  }

  const updated = await db
    .updateTable("ApprovalRequest")
    .set({ status: nextStatus })
    .where("id", "=", request.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ approvalRequest: updated });
}
