import { ok, err } from "@tailor-platform/erp-kit/core";
import type { Transaction, Updateable } from "../generated/kysely-tailordb";
import {
  DepartmentNotFoundError,
  InvalidNameError,
  SelfReferenceError,
  ParentNotFoundError,
  CircularReferenceError,
} from "../lib/errors.generated";
import { run as detectCircularReference } from "../query/detectDepartmentCircularReference";

export type UpdateDepartmentInput = {
  departmentId: string;
  name?: string;
  parentDepartmentId?: string | null;
};
/**
 * Function: updateDepartment
 *
 * Updates a department's name or parent department. Validates against
 * self-reference and circular hierarchies when reparenting.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateDepartmentInput & Omit<Partial<CF>, "status">,
) {
  const { name, parentDepartmentId, ...rest } = input;
  const customFields = { ...rest } as Record<string, unknown>;
  delete customFields.departmentId;

  // 1. Find department
  const existing = await db
    .selectFrom("Department")
    .selectAll()
    .where("id", "=", input.departmentId)
    .forUpdate()
    .executeTakeFirst();

  if (!existing) {
    return err(new DepartmentNotFoundError(input.departmentId));
  }

  // 2. Validate name if provided
  if (name !== undefined && !name?.trim()) {
    return err(new InvalidNameError(name));
  }

  // 3. Validate parentDepartmentId if provided
  if (parentDepartmentId !== undefined && parentDepartmentId !== null) {
    // Self-reference check
    if (parentDepartmentId === existing.id) {
      return err(new SelfReferenceError(existing.id));
    }

    // Find parent department
    const parent = await db
      .selectFrom("Department")
      .selectAll()
      .where("id", "=", parentDepartmentId)
      .executeTakeFirst();

    if (!parent) {
      return err(new ParentNotFoundError(parentDepartmentId));
    }

    // Verify parent belongs to same company
    if (parent.companyId !== existing.companyId) {
      return err(new ParentNotFoundError(parentDepartmentId));
    }

    // Circular reference detection via shared query
    const circularResult = await detectCircularReference(db, {
      departmentId: existing.id,
      newParentId: parentDepartmentId,
    });
    if (circularResult.ok && circularResult.value.isCircular) {
      return err(new CircularReferenceError(existing.id));
    }
  }

  // 4. Build update set — strip reserved model columns from customFields
  const RESERVED_KEYS = new Set([
    "id",
    "code",
    "companyId",
    "status",
    "name",
    "parentDepartmentId",
    "createdAt",
    "updatedAt",
  ]);
  const safeCustomFields: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(customFields)) {
    if (!RESERVED_KEYS.has(key)) {
      safeCustomFields[key] = value;
    }
  }

  const updateSet: Updateable<"Department"> = {
    ...(safeCustomFields as Updateable<"Department">),
  };

  if (name !== undefined) {
    updateSet.name = name;
  }

  if (parentDepartmentId !== undefined) {
    updateSet.parentDepartmentId = parentDepartmentId;
  }

  if (Object.keys(updateSet).length === 0) {
    return ok({ department: existing });
  }

  // 5. Update department
  const department = await db
    .updateTable("Department")
    .set(updateSet)
    .where("id", "=", existing.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ department });
}
