import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { approvalPolicyLifecycle } from "../db/approvalPolicy.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ActivePolicyConflictError,
  InvalidStatusTransitionError,
  PolicyNotFoundError,
  PolicyNotReadyToActivateError,
} from "../lib/errors.generated";

export interface ActivateApprovalPolicyInput {
  id: string;
}

export async function run(
  db: Transaction,
  input: ActivateApprovalPolicyInput,
  _ctx: CommandContext,
) {
  const policy = await db
    .selectFrom("ApprovalPolicy")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!policy) {
    return err(new PolicyNotFoundError(input.id));
  }

  const nextStatus = approvalPolicyLifecycle.tryTransition(policy.status, "activate");
  if (!nextStatus) {
    return err(new InvalidStatusTransitionError(input.id));
  }

  const steps = await db
    .selectFrom("ApprovalPolicyStep")
    .selectAll()
    .where("approvalPolicyId", "=", input.id)
    .execute();
  if (steps.length === 0) {
    return err(new PolicyNotReadyToActivateError(input.id));
  }

  const assignees = await db
    .selectFrom("ApprovalPolicyStepAssignee")
    .select("approvalPolicyStepId")
    .where(
      "approvalPolicyStepId",
      "in",
      steps.map((s) => s.id),
    )
    .execute();
  const stepsWithAssignees = new Set(assignees.map((a) => a.approvalPolicyStepId));
  if (stepsWithAssignees.size !== steps.length) {
    return err(new PolicyNotReadyToActivateError(input.id));
  }

  // Application-level enforcement: TailorDB doesn't support partial unique indexes,
  // so concurrent activates on different DRAFTs sharing (purpose, name) can race.
  const conflicting = await db
    .selectFrom("ApprovalPolicy")
    .selectAll()
    .where("purpose", "=", policy.purpose)
    .where("name", "=", policy.name)
    .where("status", "=", "ACTIVE")
    .where("id", "!=", input.id)
    .executeTakeFirst();
  if (conflicting) {
    return err(new ActivePolicyConflictError(input.id));
  }

  const now = new Date();
  const updated = await db
    .updateTable("ApprovalPolicy")
    .set({ status: nextStatus, activatedAt: now })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ approvalPolicy: updated });
}
