import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { auditPolicyLifecycle } from "../db/auditPolicy.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PolicyNotFoundError,
  InvalidStateError,
  NoActivePolicyToReplaceError,
  TargetMismatchError,
  UnauthorizedError,
} from "../lib/errors.generated";

export interface ReplaceAuditPolicyInput {
  replacementPolicyId: string;
}

/**
 * Function: replaceAuditPolicy
 *
 * Atomically replaces an existing ACTIVE audit policy with a DRAFT replacement.
 * The existing ACTIVE policy is set to INACTIVE and the replacement is set to ACTIVE.
 */
export async function run(db: Transaction, input: ReplaceAuditPolicyInput, ctx: CommandContext) {
  const { replacementPolicyId } = input;

  // 1. Find replacement policy by ID with forUpdate
  const replacement = await db
    .selectFrom("AuditPolicy")
    .selectAll()
    .where("id", "=", replacementPolicyId)
    .forUpdate()
    .executeTakeFirst();

  if (!replacement) {
    return err(new PolicyNotFoundError(replacementPolicyId));
  }

  // Scope authorization checks
  if (ctx.companyId !== undefined) {
    if (ctx.companyId !== null && replacement.companyId !== ctx.companyId) {
      return err(new UnauthorizedError(ctx.actorId));
    }
    if (ctx.companyId === null && replacement.companyId !== null) {
      return err(new UnauthorizedError(ctx.actorId));
    }
  }

  const nextReplacementStatus = auditPolicyLifecycle.tryTransition(replacement.status, "replace");
  if (!nextReplacementStatus) {
    return err(new InvalidStateError(replacementPolicyId));
  }

  // 2. Find all ACTIVE policies for the same entityName (not filtered by companyId or operationType
  //    so that TARGET_MISMATCH can fire when they differ)
  const activePolicies = await db
    .selectFrom("AuditPolicy")
    .selectAll()
    .where("entityName", "=", replacement.entityName)
    .where("status", "=", "ACTIVE")
    .forUpdate()
    .execute();

  if (activePolicies.length === 0) {
    return err(new NoActivePolicyToReplaceError(replacementPolicyId));
  }

  // 3. Find the ACTIVE policy that matches the replacement's companyId + operationType
  const existingActive = activePolicies.find(
    (p) => p.companyId === replacement.companyId && p.operationType === replacement.operationType,
  );

  if (!existingActive) {
    return err(new TargetMismatchError(replacementPolicyId));
  }

  const nextExistingStatus = auditPolicyLifecycle.tryTransition(
    existingActive.status,
    "deactivate",
  );
  if (!nextExistingStatus) {
    return err(new InvalidStateError(existingActive.id));
  }

  const deactivatedPolicy = await db
    .updateTable("AuditPolicy")
    .set({ status: nextExistingStatus })
    .where("id", "=", existingActive.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  const activatedPolicy = await db
    .updateTable("AuditPolicy")
    .set({ status: nextReplacementStatus })
    .where("id", "=", replacementPolicyId)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ deactivatedPolicy, activatedPolicy });
}
