import { Transaction } from "../generated/kysely-tailordb";
import {
  ProductNotFoundError,
  AttributeNotFoundError,
  ProductArchivedError,
  AxisStructureLockedError,
  InvalidValueError,
} from "../lib/errors.generated";
import { ok, err } from "@tailor-platform/erp-kit/module";

export interface SetProductAttributeAssignmentInput {
  productId: string;
  attributeId: string;
  valueId: string;
}

/**
 * Function: setProductAttributeAssignment
 *
 * Adds an attribute value assignment to a product (multi-value).
 * Each record is uniquely identified by (productId, attributeId, valueId).
 * If the exact triple already exists, the operation is a no-op (idempotent).
 * All attributes are variant axes, so axis locking rules always apply.
 */
export async function run(db: Transaction, input: SetProductAttributeAssignmentInput) {
  const { productId, attributeId, valueId } = input;

  // Check product exists
  const product = await db
    .selectFrom("Product")
    .selectAll()
    .where("id", "=", productId)
    .executeTakeFirst();
  if (!product) {
    return err(new ProductNotFoundError(productId));
  }

  // Check attribute exists
  const attribute = await db
    .selectFrom("ProductAttribute")
    .select("id")
    .where("id", "=", attributeId)
    .executeTakeFirst();
  if (!attribute) {
    return err(new AttributeNotFoundError(attributeId));
  }

  // Validate value: must reference a valid ProductAttributeValue for the attribute
  const attrValue = await db
    .selectFrom("ProductAttributeValue")
    .selectAll()
    .where("id", "=", valueId)
    .where("attributeId", "=", attributeId)
    .executeTakeFirst();
  if (!attrValue) {
    return err(new InvalidValueError(valueId));
  }

  // Read existing assignments (with lock for insert safety)
  const existingAssignments = await db
    .selectFrom("ProductAttributeAssignment")
    .selectAll()
    .where("productId", "=", productId)
    .where("attributeId", "=", attributeId)
    .forUpdate()
    .execute();

  // Idempotent no-op: if exact triple already exists, return existing
  const exactMatch = existingAssignments.find((a) => a.valueId === valueId);
  if (exactMatch) {
    return ok({ productAttributeAssignment: exactMatch });
  }

  // Status-based rules
  if (product.status === "ARCHIVED") {
    return err(new ProductArchivedError(productId));
  }

  if (product.status === "ACTIVE") {
    // ACTIVE: can add new values to already-assigned attribute (additive-only)
    // but cannot assign a new attribute (structure locked)
    if (existingAssignments.length === 0) {
      return err(new AxisStructureLockedError(productId));
    }
    // existingAssignments.length > 0 means attribute is already assigned → additive OK
  }

  // Insert new assignment
  const assignment = await db
    .insertInto("ProductAttributeAssignment")
    .values({
      productId,
      attributeId,
      valueId,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ productAttributeAssignment: assignment });
}
