import type { Transaction } from "../generated/kysely-tailordb";
import {
  ProductionOrderNotFoundError,
  CostSummaryNotFoundError,
  CostSummaryNotCollectingError,
  MissingInventoryIssueReferenceError,
  MissingActualUnitCostError,
  MissingActualExtendedCostError,
  MissingPostingDateError,
  IssueOutcomeNotFinalError,
  DuplicateIssueOutcomeError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface RecordInventoryIssueOutcomeInput {
  productionOrderId: string;
  inventoryIssueReference: string;
  itemReference: string;
  issuedQuantity: number;
  unitOfMeasure: string;
  actualUnitCost: number;
  actualExtendedCost: number;
  currency: string;
  valuationMethod: string;
  postingDate: string;
  siteReference: string;
  isFinalValuation: boolean;
}

/**
 * Function: recordInventoryIssueOutcome
 *
 * Applies one inventory-owned valuation outcome to the manufacturing cost
 * summary for a production order. It is the only valid path for actual
 * material cost to enter manufacturing.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: RecordInventoryIssueOutcomeInput & CF,
  _ctx: CommandContext,
) {
  const {
    productionOrderId,
    inventoryIssueReference,
    actualUnitCost,
    actualExtendedCost,
    postingDate,
    isFinalValuation,
    ...customFields
  } = input;
  void customFields;

  // 1. Validate required payload fields
  if (!inventoryIssueReference) {
    return err(new MissingInventoryIssueReferenceError(productionOrderId));
  }
  if (actualUnitCost == null) {
    return err(new MissingActualUnitCostError(productionOrderId));
  }
  if (actualExtendedCost == null) {
    return err(new MissingActualExtendedCostError(productionOrderId));
  }
  if (!postingDate) {
    return err(new MissingPostingDateError(productionOrderId));
  }

  // 2. Resolve production order
  const productionOrder = await db
    .selectFrom("ProductionOrder")
    .selectAll()
    .where("id", "=", productionOrderId)
    .forUpdate()
    .executeTakeFirst();

  if (!productionOrder) {
    return err(new ProductionOrderNotFoundError(productionOrderId));
  }

  // 3. Resolve cost summary
  const costSummary = await db
    .selectFrom("ManufacturingCostSummary")
    .selectAll()
    .where("productionOrderId", "=", productionOrderId)
    .forUpdate()
    .executeTakeFirst();

  if (!costSummary) {
    return err(new CostSummaryNotFoundError(productionOrderId));
  }

  // 4. Summary must be COLLECTING
  if (costSummary.status !== "COLLECTING") {
    return err(new CostSummaryNotCollectingError(productionOrderId));
  }

  // 5. Inventory valuation must be final
  if (!isFinalValuation) {
    return err(new IssueOutcomeNotFinalError(productionOrderId));
  }

  // 6. Check for duplicate application
  const existingCostLine = await db
    .selectFrom("ManufacturingCostLine")
    .selectAll()
    .where("costSummaryId", "=", costSummary.id)
    .where("costType", "=", `MATERIAL:${inventoryIssueReference}`)
    .executeTakeFirst();

  if (existingCostLine) {
    return err(new DuplicateIssueOutcomeError(productionOrderId));
  }

  // 7. Create a manufacturing cost line for this issue outcome
  await db
    .insertInto("ManufacturingCostLine")
    .values({
      costSummaryId: costSummary.id,
      costType: `MATERIAL:${inventoryIssueReference}`,
      plannedAmount: 0,
      actualAmount: actualExtendedCost,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  // 8. Update the cost summary's actual material cost
  const updatedSummary = await db
    .updateTable("ManufacturingCostSummary")
    .set({
      actualMaterialCost: costSummary.actualMaterialCost + actualExtendedCost,
      updatedAt: new Date(),
    })
    .where("id", "=", costSummary.id)
    .returningAll()
    .executeTakeFirst();

  return ok({ manufacturingCostSummary: updatedSummary! });
}
