import type { Selectable, Transaction } from "../generated/kysely-tailordb";
import type { defineModule as defineBusinessPartnerModule } from "../../business-partner";
import type { defineModule as defineItemManagementModule } from "../../item-management";
type BusinessPartnerModule = ReturnType<typeof defineBusinessPartnerModule>;
type BusinessPartnerQueries = BusinessPartnerModule["queries"];
type ItemManagementModule = ReturnType<typeof defineItemManagementModule>;
type ItemManagementQueries = ItemManagementModule["queries"];
import {
  PartnerNotSupplierError,
  PurchaseRequisitionAlreadyConvertedError,
  PurchaseRequisitionNotApprovedError,
  PurchaseRequisitionNotFoundError,
  SupplierNotActiveError,
  SupplierNotFoundError,
  SupplierRequiredError,
} from "../lib/errors.generated";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";

export interface ConvertPurchaseRequisitionToPurchaseOrderInput {
  id: string;
  supplierId?: string;
}

export async function run(
  db: Transaction,
  input: ConvertPurchaseRequisitionToPurchaseOrderInput,
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  // 1. Read requisition with forUpdate lock
  const requisition = await db
    .selectFrom("PurchaseRequisition")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!requisition) {
    return err(new PurchaseRequisitionNotFoundError(input.id));
  }

  // 2. Validate status
  if (requisition.status === "CONVERTED") {
    return err(new PurchaseRequisitionAlreadyConvertedError(input.id));
  }
  if (requisition.status !== "APPROVED") {
    return err(new PurchaseRequisitionNotApprovedError(input.id));
  }

  // 3. Read requisition lines
  const requisitionLines = await db
    .selectFrom("PurchaseRequisitionLine")
    .selectAll()
    .where("purchaseRequisitionId", "=", input.id)
    .execute();

  // 4. Resolve supplier: use input.supplierId, or fallback to first line's suggestedSupplierId
  const supplierId =
    input.supplierId ??
    (requisitionLines.length > 0 ? requisitionLines[0].suggestedSupplierId : null);
  if (!supplierId) {
    return err(new SupplierRequiredError(input.id));
  }

  // 5. Validate supplier (exists, active, SUPPLIER role)
  const { partner: supplier } = (
    await businessPartnerQueries.getPartner(db, { id: supplierId }, ctx)
  ).value;
  if (!supplier) {
    return err(new SupplierNotFoundError(supplierId));
  }
  if (supplier.status !== "ACTIVE") {
    return err(new SupplierNotActiveError(supplierId));
  }
  const { partnerRole } = (
    await businessPartnerQueries.getPartnerRole(
      db,
      { partnerId: supplierId, role: "SUPPLIER" },
      ctx,
    )
  ).value;
  if (!partnerRole) {
    return err(new PartnerNotSupplierError(supplierId));
  }

  // 6. Try to get supplier profile for payment term
  const companyId = requisition.companyId;
  const supplierProfile = await db
    .selectFrom("SupplierProfile")
    .selectAll()
    .where("companyId", "=", companyId)
    .where("supplierId", "=", supplierId)
    .executeTakeFirst();

  let paymentTermId: string | null = null;
  let paymentTermSnapshotLines: Selectable<"PurchaseOrder">["paymentTermSnapshotLines"] = null;
  if (supplierProfile?.defaultPurchasePaymentTermId) {
    paymentTermId = supplierProfile.defaultPurchasePaymentTermId;
    const paymentTerm = await db
      .selectFrom("PurchasePaymentTerm")
      .selectAll()
      .where("id", "=", paymentTermId)
      .executeTakeFirst();
    if (paymentTerm) {
      const termLines = await db
        .selectFrom("PurchasePaymentTermLine")
        .selectAll()
        .where("purchasePaymentTermId", "=", paymentTermId)
        .execute();
      paymentTermSnapshotLines = termLines;
    }
  }

  // 7. Resolve price rules for each line
  const priceRulesByItemId = new Map<string, { ruleId: string; unitPrice: string }>();
  for (const line of requisitionLines) {
    if (line.itemId) {
      const priceRule = await db
        .selectFrom("PurchasePriceRule")
        .selectAll()
        .where("itemId", "=", line.itemId)
        .where("status", "=", "ACTIVE")
        .executeTakeFirst();
      if (priceRule) {
        priceRulesByItemId.set(line.itemId, {
          ruleId: priceRule.id,
          unitPrice: priceRule.unitPrice,
        });
      }
    }
  }

  // 8. Create PurchaseOrder (DRAFT status)
  const purchaseOrder = await db
    .insertInto("PurchaseOrder")
    .values({
      companyId,
      supplierId,
      documentReceivingSiteId: null,
      paymentTermId,
      orderStatus: "DRAFT",
      receiptStatus: null,
      billingStatus: null,
      orderDate: new Date(),
      externalSupplierOrderReference: null,
      supplierSnapshotName: supplier.name,
      paymentTermSnapshotLines,
      rejectionReason: null,
      closeReason: null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  // 9. For each requisition line, create PurchaseOrderLine + conversion record
  for (const line of requisitionLines) {
    const priceRule = line.itemId ? priceRulesByItemId.get(line.itemId) : undefined;
    const unitPrice = priceRule ? priceRule.unitPrice : line.estimatedUnitPrice;

    let itemSnapshotName: string | null = null;
    let itemSnapshotSku: string | null = null;
    if (line.itemId) {
      const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
      if (item) {
        itemSnapshotName = item.name;
        itemSnapshotSku = item.sku ?? null;
      }
    }

    const poLine = await db
      .insertInto("PurchaseOrderLine")
      .values({
        purchaseOrderId: purchaseOrder.id,
        itemId: line.itemId ?? "",
        itemSnapshotName,
        itemSnapshotSku,
        description: line.description,
        quantity: line.quantity,
        unitPrice,
        unitId: line.unitId,
        receivingSiteId: null,
        requiresPhysicalReceipt: true,
        matchedPriceRuleId: priceRule?.ruleId ?? null,
        createdAt: new Date(),
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirstOrThrow();

    // Record the line-level allocation mapping
    await db
      .insertInto("PurchaseRequisitionLineAllocation")
      .values({
        purchaseRequisitionId: input.id,
        purchaseRequisitionLineId: line.id,
        purchaseOrderId: purchaseOrder.id,
        purchaseOrderLineId: poLine.id,
        allocatedQuantity: line.quantity,
        createdAt: new Date(),
        updatedAt: null,
      })
      .execute();
  }

  // 10. Set requisition status to CONVERTED
  const converted = await db
    .updateTable("PurchaseRequisition")
    .set({ status: "CONVERTED", updatedAt: new Date() })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ purchaseRequisition: converted, purchaseOrder });
}
