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

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

/**
 * Function: rejectApprovalStep
 *
 * Records a reject decision against a single ApprovalStepAssignee row that
 * the calling actor owns and cascades the rejection to the parent step and
 * request immediately. Trailing PENDING steps remain PENDING (no SKIPPED
 * state). The supplied reason is written verbatim to ApprovalDecision.comment
 * and ApprovalRequest.rejectionReason in the same transaction.
 */
export async function run(db: Transaction, input: RejectApprovalStepInput, 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));
  if (request.status !== "PENDING") {
    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: "REJECT",
      decidedByUserId: ctx.actorId,
      comment: input.reason,
      decidedAt: now,
    })
    .execute();

  await db
    .updateTable("ApprovalStepAssignee")
    .set({ status: "REJECTED", resolvedAt: now })
    .where("id", "=", assignee.id)
    .execute();

  await db
    .updateTable("ApprovalStep")
    .set({ status: "REJECTED", resolvedAt: now })
    .where("id", "=", step.id)
    .execute();

  const updated = await db
    .updateTable("ApprovalRequest")
    .set({
      status: "REJECTED",
      rejectionReason: input.reason,
      resolvedAt: now,
    })
    .where("id", "=", request.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ approvalRequest: updated });
}
