import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotFoundError,
  LabelAlreadyAttachedError,
  LabelNotFoundError,
  LabelNotOnItemPipelineError,
} from "../lib/errors.generated";

export interface AttachLabelToPipelineItemInput {
  itemId: string;
  labelId: string;
}

const ITEM_LABEL_OWN_KEYS = new Set(["itemId", "labelId"]);

export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: AttachLabelToPipelineItemInput & CF,
  ctx: CommandContext,
) {
  // Consumer extension fields (declared via defineModule's pipelineItemLabel.fields)
  // pass through to the insert untouched.
  const customFields: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
    if (!ITEM_LABEL_OWN_KEYS.has(key)) {
      customFields[key] = value;
    }
  }

  const item = await db
    .selectFrom("PipelineItem")
    .selectAll()
    .where("id", "=", input.itemId)
    .forUpdate()
    .executeTakeFirst();

  if (!item) {
    return err(new ItemNotFoundError(input.itemId));
  }

  const label = await db
    .selectFrom("PipelineLabel")
    .selectAll()
    .where("id", "=", input.labelId)
    .executeTakeFirst();

  if (!label) {
    return err(new LabelNotFoundError(input.labelId));
  }

  if (label.pipelineId !== item.pipelineId) {
    return err(new LabelNotOnItemPipelineError(input.labelId));
  }

  const existing = await db
    .selectFrom("PipelineItemLabel")
    .selectAll()
    .where("itemId", "=", input.itemId)
    .where("labelId", "=", input.labelId)
    .executeTakeFirst();

  if (existing) {
    return err(new LabelAlreadyAttachedError(input.labelId));
  }

  const now = new Date();

  const pipelineItemLabel = await db
    .insertInto("PipelineItemLabel")
    .values({
      ...customFields,
      itemId: input.itemId,
      labelId: input.labelId,
      createdAt: now,
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  await db
    .insertInto("PipelineItemChange")
    .values({
      itemId: input.itemId,
      changedByUserId: ctx.actorId,
      changeType: "LABEL",
      fieldName: "LABEL",
      prevValue: null,
      newValue: input.labelId,
      createdAt: now,
      updatedAt: null,
    })
    .execute();

  return ok({ pipelineItemLabel });
}
