import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { approvalRequestLifecycle } from "../db/approvalRequest.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  MissingRequiredFieldError,
  NotActiveAssigneeError,
  ParentRequestNotPendingError,
  SelfDecisionNotAllowedError,
} from "../lib/errors.generated";

export interface SendBackApprovalStepInput {
  approvalStepAssigneeId: string;
  reason: string;
}

/**
 * Function: sendBackApprovalStep
 *
 * The non-terminal counterpart to rejectApprovalStep. Records a SEND_BACK
 * decision against a single ApprovalStepAssignee row the actor owns and moves
 * the parent request to REVISION_REQUESTED, returning it to the requester. The
 * supplied reason is written to ApprovalDecision.comment. Unlike reject it does
 * not cascade: ApprovalStep and ApprovalStepAssignee rows are left untouched and
 * are reset only when the requester calls resubmitApprovalRequest.
 */
export async function run(db: Transaction, input: SendBackApprovalStepInput, ctx: CommandContext) {
  const assignee = await db
    .selectFrom("ApprovalStepAssignee")
    .selectAll()
    .where("id", "=", input.approvalStepAssigneeId)
    .forUpdate()
    .executeTakeFirst();
  if (!assignee) return err(new NotActiveAssigneeError(input.approvalStepAssigneeId));

  if (assignee.status !== "PENDING" || assignee.userId !== ctx.actorId) {
    return err(new NotActiveAssigneeError(input.approvalStepAssigneeId));
  }

  const step = await db
    .selectFrom("ApprovalStep")
    .selectAll()
    .where("id", "=", assignee.approvalStepId)
    .forUpdate()
    .executeTakeFirst();
  if (!step || step.status !== "IN_PROGRESS") {
    return err(new NotActiveAssigneeError(input.approvalStepAssigneeId));
  }

  const request = await db
    .selectFrom("ApprovalRequest")
    .selectAll()
    .where("id", "=", step.approvalRequestId)
    .forUpdate()
    .executeTakeFirst();
  if (!request) return err(new NotActiveAssigneeError(input.approvalStepAssigneeId));
  const nextStatus = approvalRequestLifecycle.tryTransition(request.status, "sendBack");
  if (!nextStatus) {
    return err(new ParentRequestNotPendingError(request.id));
  }

  if (ctx.actorId === request.requesterId) {
    return err(new SelfDecisionNotAllowedError(request.id));
  }

  if (input.reason.trim() === "") {
    return err(new MissingRequiredFieldError("reason"));
  }

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

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

  return ok({ approvalRequest: updated });
}
