import type { Transaction } from "../generated/kysely-tailordb";
import {
  AccountGroupNotFoundError,
  ChartOfAccountsArchivedError,
  ParentGroupNotFoundError,
  CircularReferenceError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface MoveAccountGroupInput {
  id: string;
  newParentAccountGroupId: string | null;
}

/**
 * Function: moveAccountGroup
 *
 * Reparents an account group by changing its parent reference within the same
 * CoA. Moving to a null parent promotes the group to a root group.
 */
export async function run(db: Transaction, input: MoveAccountGroupInput, _ctx: CommandContext) {
  const { id, newParentAccountGroupId } = input;

  // 1. Find the group
  const existing = await db
    .selectFrom("AccountGroup")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!existing) {
    return err(new AccountGroupNotFoundError(id));
  }

  // 2. Check parent CoA status
  const coa = await db
    .selectFrom("ChartOfAccounts")
    .selectAll()
    .where("id", "=", existing.chartOfAccountsId)
    .executeTakeFirst();

  if (coa!.status === "ARCHIVED") {
    return err(new ChartOfAccountsArchivedError(existing.chartOfAccountsId));
  }

  // 3. If new parent specified, validate it
  if (newParentAccountGroupId !== null) {
    // Check self-reference
    if (newParentAccountGroupId === id) {
      return err(new CircularReferenceError(id));
    }

    const newParent = await db
      .selectFrom("AccountGroup")
      .selectAll()
      .where("id", "=", newParentAccountGroupId)
      .executeTakeFirst();

    if (!newParent) {
      return err(new ParentGroupNotFoundError(newParentAccountGroupId));
    }

    if (newParent.chartOfAccountsId !== existing.chartOfAccountsId) {
      return err(new ParentGroupNotFoundError(newParentAccountGroupId));
    }

    // 4. Check for circular reference: walk up from newParent to root
    let currentParentId = newParent.parentAccountGroupId;
    while (currentParentId !== null) {
      if (currentParentId === id) {
        return err(new CircularReferenceError(id));
      }
      const ancestor = await db
        .selectFrom("AccountGroup")
        .selectAll()
        .where("id", "=", currentParentId)
        .executeTakeFirst();

      if (!ancestor) break;
      currentParentId = ancestor.parentAccountGroupId;
    }
  }

  // 5. Update parent reference
  const previousParentId = existing.parentAccountGroupId;
  const accountGroup = await db
    .updateTable("AccountGroup")
    .set({
      parentAccountGroupId: newParentAccountGroupId,
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  return ok({ accountGroup: accountGroup!, previousParentAccountGroupId: previousParentId });
}
