import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import { serialNumberLifecycle } from "../db/serialNumber.lifecycle.generated";
import { stockAdjustmentLifecycle } from "../db/stockAdjustment.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  StockAdjustmentNotFoundError,
  InvalidStatusError,
  InsufficientOnHandError,
  InsufficientAvailableStockError,
  InsufficientBlockedStockError,
  InvalidSerialStateForScrapError,
} from "../lib/errors.generated";
import { applyValuation } from "../lib/valuation";

export interface ConfirmStockAdjustmentInput {
  id: string;
}

/**
 * Function: confirmStockAdjustment
 *
 * Approves and executes a submitted stock adjustment, creating InventoryLedger
 * entries and updating StockLevel records for each adjustment line.
 * Combines approval and execution into a single step.
 */
export async function run(
  db: Transaction,
  input: ConfirmStockAdjustmentInput,
  _ctx: CommandContext,
) {
  const { id } = input;

  // 1. Load StockAdjustment
  const stockAdjustment = await db
    .selectFrom("StockAdjustment")
    .selectAll()
    .where("id", "=", id)
    .executeTakeFirst();

  if (!stockAdjustment) {
    return err(new StockAdjustmentNotFoundError(id));
  }

  // 2. Validate transition is allowed
  const nextAdjustmentStatus = stockAdjustmentLifecycle.tryTransition(
    stockAdjustment.status,
    "confirm",
  );
  if (!nextAdjustmentStatus) {
    return err(new InvalidStatusError(id));
  }

  // 3. Load all lines for this adjustment
  const lines = (await db
    .selectFrom("StockAdjustmentLine")
    .selectAll()
    .where("stockAdjustmentId", "=", id)
    .execute()) as {
    id: string;
    stockAdjustmentId: string;
    itemId: string;
    storageLocationId: string;
    quantity: string;
    adjustmentDirection: string | null;
    fromStockCategory: string | null;
    unitCost: string | null;
    lotId: string | null;
    serialNumberId: string | null;
  }[];

  const now = new Date();
  const allLedgerEntries: {
    id: string;
    sourceType: string;
    dcIndicator: string;
    amount: string | null;
  }[] = [];

  // 4. Process each line based on adjustmentType
  for (const line of lines) {
    const { itemId, storageLocationId, quantity, lotId, serialNumberId, unitCost } = line;
    const adjustmentType = stockAdjustment.adjustmentType;

    if (adjustmentType === "CORRECTION") {
      if (line.adjustmentDirection === "INCREASE") {
        // --- CORRECTION INCREASE ---
        const stockLevel = await db
          .selectFrom("StockLevel")
          .selectAll()
          .where("itemId", "=", itemId)
          .where("storageLocationId", "=", storageLocationId)
          .forUpdate()
          .executeTakeFirst();

        // Create ledger entry: CORRECTION / DR
        const amount = unitCost != null ? new Decimal(quantity).mul(unitCost).toString() : null;
        const ledgerEntry = await db
          .insertInto("InventoryLedger")
          .values({
            sourceType: "CORRECTION",
            sourceId: stockAdjustment.id,
            dcIndicator: "DR",
            itemId,
            storageLocationId,
            quantity,
            amount,
            executedAt: now,
            lotId: lotId ?? null,
            serialNumberId: serialNumberId ?? null,
            createdAt: now,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        allLedgerEntries.push(ledgerEntry);

        // Update or create StockLevel
        if (stockLevel) {
          await db
            .updateTable("StockLevel")
            .set({
              onHand: new Decimal(stockLevel.onHand).plus(quantity).toString(),
              updatedAt: now,
            })
            .where("id", "=", stockLevel.id)
            .returningAll()
            .executeTakeFirst();
        } else {
          await db
            .insertInto("StockLevel")
            .values({
              itemId,
              storageLocationId,
              onHand: quantity,
              reserved: "0",
              incoming: "0",
              blocked: "0",
              createdAt: now,
              updatedAt: null,
            })
            .returningAll()
            .executeTakeFirst();
        }

        // Update LotStockLevel if lotId
        if (lotId) {
          const lotStockLevel = await db
            .selectFrom("LotStockLevel")
            .selectAll()
            .where("lotId", "=", lotId)
            .where("storageLocationId", "=", storageLocationId)
            .forUpdate()
            .executeTakeFirst();

          if (lotStockLevel) {
            await db
              .updateTable("LotStockLevel")
              .set({
                onHand: new Decimal(lotStockLevel.onHand).plus(quantity).toString(),
                updatedAt: now,
              })
              .where("id", "=", lotStockLevel.id)
              .returningAll()
              .executeTakeFirst();
          } else {
            await db
              .insertInto("LotStockLevel")
              .values({
                lotId,
                storageLocationId,
                onHand: quantity,
                blocked: "0",
                createdAt: now,
                updatedAt: null,
              })
              .returningAll()
              .executeTakeFirst();
          }
        }

        // Apply valuation (DR)
        await applyValuation(db, {
          itemId,
          inventoryLedgerId: ledgerEntry.id,
          dcIndicator: "DR",
          quantity,
          unitCost: unitCost ?? undefined,
        });
      } else {
        // --- CORRECTION DECREASE ---
        const stockLevel = await db
          .selectFrom("StockLevel")
          .selectAll()
          .where("itemId", "=", itemId)
          .where("storageLocationId", "=", storageLocationId)
          .forUpdate()
          .executeTakeFirst();

        const onHand = stockLevel ? stockLevel.onHand : "0";
        if (new Decimal(onHand).lt(quantity)) {
          return err(new InsufficientOnHandError(`${itemId}:${storageLocationId}`));
        }

        // Create ledger entry: CORRECTION / CR
        const amount = unitCost != null ? new Decimal(quantity).mul(unitCost).toString() : null;
        const ledgerEntry = await db
          .insertInto("InventoryLedger")
          .values({
            sourceType: "CORRECTION",
            sourceId: stockAdjustment.id,
            dcIndicator: "CR",
            itemId,
            storageLocationId,
            quantity,
            amount,
            executedAt: now,
            lotId: lotId ?? null,
            serialNumberId: serialNumberId ?? null,
            createdAt: now,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        allLedgerEntries.push(ledgerEntry);

        // Update StockLevel
        if (stockLevel) {
          await db
            .updateTable("StockLevel")
            .set({
              onHand: new Decimal(stockLevel.onHand).minus(quantity).toString(),
              updatedAt: now,
            })
            .where("id", "=", stockLevel.id)
            .returningAll()
            .executeTakeFirst();
        }

        // Update LotStockLevel if lotId
        if (lotId) {
          const lotStockLevel = await db
            .selectFrom("LotStockLevel")
            .selectAll()
            .where("lotId", "=", lotId)
            .where("storageLocationId", "=", storageLocationId)
            .forUpdate()
            .executeTakeFirst();

          if (lotStockLevel) {
            await db
              .updateTable("LotStockLevel")
              .set({
                onHand: Decimal.max(
                  0,
                  new Decimal(lotStockLevel.onHand).minus(quantity),
                ).toString(),
                updatedAt: now,
              })
              .where("id", "=", lotStockLevel.id)
              .returningAll()
              .executeTakeFirst();
          }
        }

        // Apply valuation (CR)
        await applyValuation(db, {
          itemId,
          inventoryLedgerId: ledgerEntry.id,
          dcIndicator: "CR",
          quantity,
          unitCost: unitCost ?? undefined,
        });
      }
    } else if (adjustmentType === "SCRAP") {
      const fromCategory = line.fromStockCategory;

      if (fromCategory === "BLOCKED") {
        // --- SCRAP from BLOCKED ---
        const stockLevel = await db
          .selectFrom("StockLevel")
          .selectAll()
          .where("itemId", "=", itemId)
          .where("storageLocationId", "=", storageLocationId)
          .forUpdate()
          .executeTakeFirst();

        const blocked = stockLevel ? stockLevel.blocked : "0";
        if (new Decimal(blocked).lt(quantity)) {
          return err(new InsufficientBlockedStockError(`${itemId}:${storageLocationId}`));
        }

        // Create 2 ledger entries: SCRAP/CR + UNBLOCK/CR
        const amount = unitCost != null ? new Decimal(quantity).mul(unitCost).toString() : null;
        const scrapEntry = await db
          .insertInto("InventoryLedger")
          .values({
            sourceType: "SCRAP",
            sourceId: stockAdjustment.id,
            dcIndicator: "CR",
            itemId,
            storageLocationId,
            quantity,
            amount,
            executedAt: now,
            lotId: lotId ?? null,
            serialNumberId: serialNumberId ?? null,
            createdAt: now,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        allLedgerEntries.push(scrapEntry);

        const unblockEntry = await db
          .insertInto("InventoryLedger")
          .values({
            sourceType: "UNBLOCK",
            sourceId: stockAdjustment.id,
            dcIndicator: "CR",
            itemId,
            storageLocationId,
            quantity,
            amount: null,
            executedAt: now,
            lotId: lotId ?? null,
            serialNumberId: serialNumberId ?? null,
            createdAt: now,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        allLedgerEntries.push(unblockEntry);

        // Update StockLevel: onHand -= qty, blocked -= qty
        if (stockLevel) {
          await db
            .updateTable("StockLevel")
            .set({
              onHand: new Decimal(stockLevel.onHand).minus(quantity).toString(),
              blocked: new Decimal(stockLevel.blocked).minus(quantity).toString(),
              updatedAt: now,
            })
            .where("id", "=", stockLevel.id)
            .returningAll()
            .executeTakeFirst();
        }

        // Update LotStockLevel if lotId
        if (lotId) {
          const lotStockLevel = await db
            .selectFrom("LotStockLevel")
            .selectAll()
            .where("lotId", "=", lotId)
            .where("storageLocationId", "=", storageLocationId)
            .forUpdate()
            .executeTakeFirst();

          if (lotStockLevel) {
            await db
              .updateTable("LotStockLevel")
              .set({
                onHand: Decimal.max(
                  0,
                  new Decimal(lotStockLevel.onHand).minus(quantity),
                ).toString(),
                blocked: Decimal.max(
                  0,
                  new Decimal(lotStockLevel.blocked).minus(quantity),
                ).toString(),
                updatedAt: now,
              })
              .where("id", "=", lotStockLevel.id)
              .returningAll()
              .executeTakeFirst();
          }
        }

        // Apply valuation (CR)
        await applyValuation(db, {
          itemId,
          inventoryLedgerId: scrapEntry.id,
          dcIndicator: "CR",
          quantity,
        });

        // Serial number: AVAILABLE or RETURNED -> SCRAPPED
        if (serialNumberId) {
          const serial = await db
            .selectFrom("SerialNumber")
            .selectAll()
            .where("id", "=", serialNumberId)
            .forUpdate()
            .executeTakeFirst();

          if (serial) {
            const nextSerialStatus = serialNumberLifecycle.tryTransition(serial.status, "scrap");
            if (!nextSerialStatus) {
              return err(new InvalidSerialStateForScrapError(serialNumberId));
            }
            await db
              .updateTable("SerialNumber")
              .set({ status: nextSerialStatus, updatedAt: now })
              .where("id", "=", serial.id)
              .returningAll()
              .executeTakeFirst();
          }
        }
      } else {
        // --- SCRAP from AVAILABLE (default) ---
        const stockLevel = await db
          .selectFrom("StockLevel")
          .selectAll()
          .where("itemId", "=", itemId)
          .where("storageLocationId", "=", storageLocationId)
          .forUpdate()
          .executeTakeFirst();

        const onHand = stockLevel ? stockLevel.onHand : "0";
        const blocked = stockLevel ? stockLevel.blocked : "0";
        if (new Decimal(onHand).minus(blocked).lt(quantity)) {
          return err(new InsufficientOnHandError(`${itemId}:${storageLocationId}`));
        }

        // Create 1 ledger entry: SCRAP / CR
        const amount = unitCost != null ? new Decimal(quantity).mul(unitCost).toString() : null;
        const ledgerEntry = await db
          .insertInto("InventoryLedger")
          .values({
            sourceType: "SCRAP",
            sourceId: stockAdjustment.id,
            dcIndicator: "CR",
            itemId,
            storageLocationId,
            quantity,
            amount,
            executedAt: now,
            lotId: lotId ?? null,
            serialNumberId: serialNumberId ?? null,
            createdAt: now,
          })
          .returningAll()
          .executeTakeFirstOrThrow();

        allLedgerEntries.push(ledgerEntry);

        // Update StockLevel: onHand -= qty
        if (stockLevel) {
          await db
            .updateTable("StockLevel")
            .set({
              onHand: new Decimal(stockLevel.onHand).minus(quantity).toString(),
              updatedAt: now,
            })
            .where("id", "=", stockLevel.id)
            .returningAll()
            .executeTakeFirst();
        }

        // Update LotStockLevel if lotId
        if (lotId) {
          const lotStockLevel = await db
            .selectFrom("LotStockLevel")
            .selectAll()
            .where("lotId", "=", lotId)
            .where("storageLocationId", "=", storageLocationId)
            .forUpdate()
            .executeTakeFirst();

          if (lotStockLevel) {
            await db
              .updateTable("LotStockLevel")
              .set({
                onHand: Decimal.max(
                  0,
                  new Decimal(lotStockLevel.onHand).minus(quantity),
                ).toString(),
                updatedAt: now,
              })
              .where("id", "=", lotStockLevel.id)
              .returningAll()
              .executeTakeFirst();
          }
        }

        // Apply valuation (CR)
        await applyValuation(db, {
          itemId,
          inventoryLedgerId: ledgerEntry.id,
          dcIndicator: "CR",
          quantity,
        });

        // Serial number: AVAILABLE or RETURNED -> SCRAPPED
        if (serialNumberId) {
          const serial = await db
            .selectFrom("SerialNumber")
            .selectAll()
            .where("id", "=", serialNumberId)
            .forUpdate()
            .executeTakeFirst();

          if (serial) {
            const nextSerialStatus = serialNumberLifecycle.tryTransition(serial.status, "scrap");
            if (!nextSerialStatus) {
              return err(new InvalidSerialStateForScrapError(serialNumberId));
            }
            await db
              .updateTable("SerialNumber")
              .set({ status: nextSerialStatus, updatedAt: now })
              .where("id", "=", serial.id)
              .returningAll()
              .executeTakeFirst();
          }
        }
      }
    } else if (adjustmentType === "BLOCK") {
      // --- BLOCK ---
      const stockLevel = await db
        .selectFrom("StockLevel")
        .selectAll()
        .where("itemId", "=", itemId)
        .where("storageLocationId", "=", storageLocationId)
        .forUpdate()
        .executeTakeFirst();

      const onHand = stockLevel ? stockLevel.onHand : "0";
      const reserved = stockLevel ? stockLevel.reserved : "0";
      const blocked = stockLevel ? stockLevel.blocked : "0";
      const available = new Decimal(onHand).minus(reserved).minus(blocked);

      if (available.lt(quantity)) {
        return err(new InsufficientAvailableStockError(`${itemId}:${storageLocationId}`));
      }

      // Create 2 ledger entries: BLOCK/CR + BLOCK/DR
      const blockCR = await db
        .insertInto("InventoryLedger")
        .values({
          sourceType: "BLOCK",
          sourceId: stockAdjustment.id,
          dcIndicator: "CR",
          itemId,
          storageLocationId,
          quantity,
          amount: null,
          executedAt: now,
          lotId: lotId ?? null,
          serialNumberId: serialNumberId ?? null,
          createdAt: now,
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      allLedgerEntries.push(blockCR);

      const blockDR = await db
        .insertInto("InventoryLedger")
        .values({
          sourceType: "BLOCK",
          sourceId: stockAdjustment.id,
          dcIndicator: "DR",
          itemId,
          storageLocationId,
          quantity,
          amount: null,
          executedAt: now,
          lotId: lotId ?? null,
          serialNumberId: serialNumberId ?? null,
          createdAt: now,
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      allLedgerEntries.push(blockDR);

      // Update StockLevel: blocked += qty (onHand unchanged)
      if (stockLevel) {
        await db
          .updateTable("StockLevel")
          .set({
            blocked: new Decimal(stockLevel.blocked).plus(quantity).toString(),
            updatedAt: now,
          })
          .where("id", "=", stockLevel.id)
          .returningAll()
          .executeTakeFirst();
      }

      // Update LotStockLevel if lotId
      if (lotId) {
        const lotStockLevel = await db
          .selectFrom("LotStockLevel")
          .selectAll()
          .where("lotId", "=", lotId)
          .where("storageLocationId", "=", storageLocationId)
          .forUpdate()
          .executeTakeFirst();

        if (lotStockLevel) {
          await db
            .updateTable("LotStockLevel")
            .set({
              blocked: new Decimal(lotStockLevel.blocked).plus(quantity).toString(),
              updatedAt: now,
            })
            .where("id", "=", lotStockLevel.id)
            .returningAll()
            .executeTakeFirst();
        } else {
          await db
            .insertInto("LotStockLevel")
            .values({
              lotId,
              storageLocationId,
              onHand: "0",
              blocked: quantity,
              createdAt: now,
              updatedAt: null,
            })
            .returningAll()
            .executeTakeFirst();
        }
      }

      // No valuation call for BLOCK
    } else if (adjustmentType === "UNBLOCK") {
      // --- UNBLOCK ---
      const stockLevel = await db
        .selectFrom("StockLevel")
        .selectAll()
        .where("itemId", "=", itemId)
        .where("storageLocationId", "=", storageLocationId)
        .forUpdate()
        .executeTakeFirst();

      const blocked = stockLevel ? stockLevel.blocked : "0";
      if (new Decimal(blocked).lt(quantity)) {
        return err(new InsufficientBlockedStockError(`${itemId}:${storageLocationId}`));
      }

      // Create 2 ledger entries: UNBLOCK/DR + UNBLOCK/CR
      const unblockDR = await db
        .insertInto("InventoryLedger")
        .values({
          sourceType: "UNBLOCK",
          sourceId: stockAdjustment.id,
          dcIndicator: "DR",
          itemId,
          storageLocationId,
          quantity,
          amount: null,
          executedAt: now,
          lotId: lotId ?? null,
          serialNumberId: serialNumberId ?? null,
          createdAt: now,
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      allLedgerEntries.push(unblockDR);

      const unblockCR = await db
        .insertInto("InventoryLedger")
        .values({
          sourceType: "UNBLOCK",
          sourceId: stockAdjustment.id,
          dcIndicator: "CR",
          itemId,
          storageLocationId,
          quantity,
          amount: null,
          executedAt: now,
          lotId: lotId ?? null,
          serialNumberId: serialNumberId ?? null,
          createdAt: now,
        })
        .returningAll()
        .executeTakeFirstOrThrow();

      allLedgerEntries.push(unblockCR);

      // Update StockLevel: blocked -= qty (onHand unchanged)
      if (stockLevel) {
        await db
          .updateTable("StockLevel")
          .set({
            blocked: new Decimal(stockLevel.blocked).minus(quantity).toString(),
            updatedAt: now,
          })
          .where("id", "=", stockLevel.id)
          .returningAll()
          .executeTakeFirst();
      }

      // Update LotStockLevel if lotId
      if (lotId) {
        const lotStockLevel = await db
          .selectFrom("LotStockLevel")
          .selectAll()
          .where("lotId", "=", lotId)
          .where("storageLocationId", "=", storageLocationId)
          .forUpdate()
          .executeTakeFirst();

        if (lotStockLevel) {
          await db
            .updateTable("LotStockLevel")
            .set({
              blocked: Decimal.max(
                0,
                new Decimal(lotStockLevel.blocked).minus(quantity),
              ).toString(),
              updatedAt: now,
            })
            .where("id", "=", lotStockLevel.id)
            .returningAll()
            .executeTakeFirst();
        }
      }

      // No valuation call for UNBLOCK
    }
  }

  // 5. Update StockAdjustment status to CONFIRMED
  const updatedAdjustment = await db
    .updateTable("StockAdjustment")
    .set({
      status: nextAdjustmentStatus,
      updatedAt: now,
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({
    stockAdjustment: updatedAdjustment,
    inventoryLedgerEntries: allLedgerEntries,
  });
}
