import { Transaction } from "../generated/kysely-tailordb";
import {
  ProductNotFoundError,
  NoFieldsToUpdateError,
  UomLockedError,
  InvalidUomError,
  MissingRequiredFieldError,
} from "../lib/errors.generated";
import type { PrimitivesQueries } from "../module";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export type UpdateProductInput = (
  | {
      id: string;
    }
  | {
      code: string;
    }
) & {
  name?: string;
  description?: string;
  unitId?: string;
};

/**
 * Function: updateProduct
 *
 * Updates mutable fields of an existing product.
 * The product can be looked up by id or code. UoM can only be changed in DRAFT status.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateProductInput & Omit<Partial<CF>, "status">,
  ctx: CommandContext,
  primitivesQueries: Pick<PrimitivesQueries, "getUnit">,
) {
  const { name, description, unitId } = input;

  const KNOWN_KEYS = new Set(["id", "code", "name", "description", "unitId"]);
  const customFields: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
    if (!KNOWN_KEYS.has(key)) {
      customFields[key] = value;
    }
  }

  // 1. Check product exists (lookup by id or code)
  const product =
    "id" in input
      ? await db
          .selectFrom("Product")
          .selectAll()
          .where("id", "=", (input as { id: string }).id)
          .forUpdate()
          .executeTakeFirst()
      : await db
          .selectFrom("Product")
          .selectAll()
          .where("code", "=", (input as { code: string }).code)
          .forUpdate()
          .executeTakeFirst();

  if (!product) {
    const key = "id" in input ? (input as { id: string }).id : (input as { code: string }).code;
    return err(new ProductNotFoundError(key));
  }

  // 2. Check if any fields to update
  const hasNameUpdate = name !== undefined;
  const hasDescriptionUpdate = description !== undefined;
  const hasUnitUpdate = unitId !== undefined;
  const hasCustomFields = Object.keys(customFields).length > 0;

  if (!hasNameUpdate && !hasDescriptionUpdate && !hasUnitUpdate && !hasCustomFields) {
    return err(new NoFieldsToUpdateError(product.id));
  }

  // 3. Validate name if provided
  if (hasNameUpdate && !name?.trim()) {
    return err(new MissingRequiredFieldError("name"));
  }

  // 4. UoM can only be updated in DRAFT status
  if (hasUnitUpdate) {
    if (product.status !== "DRAFT") {
      return err(new UomLockedError(product.id));
    }
    const unitResult = await primitivesQueries.getUnit(db, { id: unitId }, ctx);
    if (!unitResult.ok) return err(new InvalidUomError(unitId));
    if (!unitResult.value.unit?.isActive) {
      return err(new InvalidUomError(unitId));
    }
  }

  // 5. Build update payload
  const updates: Record<string, unknown> = {
    ...customFields,
    updatedAt: new Date(),
  };
  if (hasNameUpdate) updates.name = name;
  if (hasDescriptionUpdate) updates.description = description ?? null;
  if (hasUnitUpdate) updates.unitId = unitId;

  const updated = await db
    .updateTable("Product")
    .set(updates)
    .where("id", "=", product.id)
    .returningAll()
    .executeTakeFirst();

  return ok({ product: updated! });
}
