import type { Transaction } from "../generated/kysely-tailordb";
import {
  StockMovementNotFoundError,
  StockMovementNotConfirmedError,
  InsufficientStockError,
  InsufficientBlockedStockError,
  LotStockInsufficientError,
  SerialInvalidStateError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface ExecuteStockMovementInput {
  id: string;
}

/**
 * Function: executeStockMovement
 *
 * Transitions a stock movement from CONFIRMED to DONE.
 * Updates stock levels, lot stock levels, and serial number states.
 */
export async function run(db: Transaction, input: ExecuteStockMovementInput, _ctx: CommandContext) {
  const { id } = input;

  // 1. Check movement exists
  const movement = await db
    .selectFrom("StockMovement")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!movement) {
    return err(new StockMovementNotFoundError(id));
  }

  // 2. Must be CONFIRMED
  if (movement.status !== "CONFIRMED") {
    return err(new StockMovementNotConfirmedError(id));
  }

  // 3. Get all move lines
  const moveLines = await db
    .selectFrom("StockMoveLine")
    .selectAll()
    .where("stockMovementId", "=", id)
    .execute();

  // 4. Process each move line
  for (const line of moveLines) {
    const { movementType } = movement;

    // Process stock level updates
    if (movementType === "GOODS_RECEIPT") {
      // Increase on-hand at destination, decrease incoming
      await upsertStockLevel(db, line.itemId, line.destinationLocationId!, (sl) => ({
        onHand: sl.onHand + line.quantity,
        incoming: sl.incoming - line.quantity,
      }));
    } else if (movementType === "GOODS_ISSUE") {
      // Check sufficient available stock
      const sourceStock = await getStockLevel(db, line.itemId, line.sourceLocationId!);
      const available = sourceStock
        ? sourceStock.onHand - sourceStock.reserved - sourceStock.blocked
        : 0;
      if (available < line.quantity) {
        return err(new InsufficientStockError(line.itemId));
      }
      // Decrease on-hand at source
      await upsertStockLevel(db, line.itemId, line.sourceLocationId!, (sl) => ({
        onHand: sl.onHand - line.quantity,
      }));
    } else if (movementType === "INTERNAL_TRANSFER") {
      // Decrease source, increase destination
      await upsertStockLevel(db, line.itemId, line.sourceLocationId!, (sl) => ({
        onHand: sl.onHand - line.quantity,
      }));
      await upsertStockLevel(db, line.itemId, line.destinationLocationId!, (sl) => ({
        onHand: sl.onHand + line.quantity,
      }));
    } else if (movementType === "BLOCK") {
      // Check sufficient available stock
      const sourceStock = await getStockLevel(db, line.itemId, line.sourceLocationId!);
      const available = sourceStock
        ? sourceStock.onHand - sourceStock.reserved - sourceStock.blocked
        : 0;
      if (available < line.quantity) {
        return err(new InsufficientStockError(line.itemId));
      }
      // Increase blocked
      await upsertStockLevel(db, line.itemId, line.sourceLocationId!, (sl) => ({
        blocked: sl.blocked + line.quantity,
      }));
    } else if (movementType === "UNBLOCK") {
      // Check sufficient blocked stock
      const sourceStock = await getStockLevel(db, line.itemId, line.sourceLocationId!);
      if (!sourceStock || sourceStock.blocked < line.quantity) {
        return err(new InsufficientBlockedStockError(line.itemId));
      }
      // Decrease blocked
      await upsertStockLevel(db, line.itemId, line.sourceLocationId!, (sl) => ({
        blocked: sl.blocked - line.quantity,
      }));
    }

    // Process lot stock level updates
    if (line.lotId) {
      if (movementType === "GOODS_RECEIPT") {
        await upsertLotStockLevel(db, line.lotId, line.destinationLocationId!, (lsl) => ({
          onHand: lsl.onHand + line.quantity,
        }));
      } else if (movementType === "GOODS_ISSUE") {
        const lotStock = await getLotStockLevel(db, line.lotId, line.sourceLocationId!);
        if (!lotStock || lotStock.onHand < line.quantity) {
          return err(new LotStockInsufficientError(line.lotId));
        }
        await upsertLotStockLevel(db, line.lotId, line.sourceLocationId!, (lsl) => ({
          onHand: lsl.onHand - line.quantity,
        }));
      } else if (movementType === "INTERNAL_TRANSFER") {
        const lotStock = await getLotStockLevel(db, line.lotId, line.sourceLocationId!);
        if (!lotStock || lotStock.onHand < line.quantity) {
          return err(new LotStockInsufficientError(line.lotId));
        }
        await upsertLotStockLevel(db, line.lotId, line.sourceLocationId!, (lsl) => ({
          onHand: lsl.onHand - line.quantity,
        }));
        await upsertLotStockLevel(db, line.lotId, line.destinationLocationId!, (lsl) => ({
          onHand: lsl.onHand + line.quantity,
        }));
      }
    }

    // Process serial number state transitions
    if (line.serialNumberId) {
      const serial = await db
        .selectFrom("SerialNumber")
        .selectAll()
        .where("id", "=", line.serialNumberId)
        .forUpdate()
        .executeTakeFirst();

      if (serial) {
        let newStatus: "AVAILABLE" | "ISSUED" | "RETURNED" | null = null;

        if (movementType === "GOODS_ISSUE") {
          if (serial.status !== "AVAILABLE") {
            return err(new SerialInvalidStateError(line.serialNumberId));
          }
          newStatus = "ISSUED";
        } else if (movementType === "GOODS_RECEIPT") {
          if (serial.status === "ISSUED") {
            // Customer return: ISSUED -> RETURNED
            newStatus = "RETURNED";
          } else if (serial.status === "RETURNED") {
            // Restock after return: RETURNED -> AVAILABLE
            newStatus = "AVAILABLE";
          } else {
            // Invalid state for goods receipt serial transition
            return err(new SerialInvalidStateError(line.serialNumberId));
          }
        }

        if (newStatus) {
          await db
            .updateTable("SerialNumber")
            .set({
              status: newStatus,
              updatedAt: new Date(),
            })
            .where("id", "=", line.serialNumberId)
            .execute();
        }
      }
    }
  }

  // 5. Trigger valuation recalculation
  // Get all unique item IDs from move lines
  const itemIds = [...new Set(moveLines.map((l) => l.itemId))];
  for (const itemId of itemIds) {
    const valuation = await db
      .selectFrom("ItemValuation")
      .selectAll()
      .where("itemId", "=", itemId)
      .forUpdate()
      .executeTakeFirst();

    if (valuation) {
      await db
        .updateTable("ItemValuation")
        .set({ updatedAt: new Date() })
        .where("id", "=", valuation.id)
        .execute();
    }
  }

  // 6. Update status to DONE
  const executed = await db
    .updateTable("StockMovement")
    .set({
      status: "DONE",
      executedAt: new Date(),
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  return ok({ stockMovement: executed! });
}

// Helper: get stock level for item at location
async function getStockLevel(db: Transaction, itemId: string, locationId: string) {
  return db
    .selectFrom("StockLevel")
    .selectAll()
    .where("itemId", "=", itemId)
    .where("storageLocationId", "=", locationId)
    .forUpdate()
    .executeTakeFirst();
}

// Helper: upsert stock level
async function upsertStockLevel(
  db: Transaction,
  itemId: string,
  locationId: string,
  updater: (current: {
    onHand: number;
    reserved: number;
    incoming: number;
    blocked: number;
  }) => Partial<{ onHand: number; reserved: number; incoming: number; blocked: number }>,
) {
  const existing = await getStockLevel(db, itemId, locationId);
  const current = existing ?? { onHand: 0, reserved: 0, incoming: 0, blocked: 0 };
  const updates = updater(current);

  if (existing) {
    await db
      .updateTable("StockLevel")
      .set({ ...updates, updatedAt: new Date() })
      .where("id", "=", existing.id)
      .execute();
  } else {
    await db
      .insertInto("StockLevel")
      .values({
        itemId,
        storageLocationId: locationId,
        onHand: updates.onHand ?? 0,
        reserved: updates.reserved ?? 0,
        incoming: updates.incoming ?? 0,
        blocked: updates.blocked ?? 0,
        createdAt: new Date(),
        updatedAt: null,
      })
      .execute();
  }
}

// Helper: get lot stock level
async function getLotStockLevel(db: Transaction, lotId: string, locationId: string) {
  return db
    .selectFrom("LotStockLevel")
    .selectAll()
    .where("lotId", "=", lotId)
    .where("storageLocationId", "=", locationId)
    .forUpdate()
    .executeTakeFirst();
}

// Helper: upsert lot stock level
async function upsertLotStockLevel(
  db: Transaction,
  lotId: string,
  locationId: string,
  updater: (current: { onHand: number }) => { onHand: number },
) {
  const existing = await getLotStockLevel(db, lotId, locationId);
  const current = existing ?? { onHand: 0 };
  const updates = updater(current);

  if (existing) {
    await db
      .updateTable("LotStockLevel")
      .set({ ...updates, updatedAt: new Date() })
      .where("id", "=", existing.id)
      .execute();
  } else {
    await db
      .insertInto("LotStockLevel")
      .values({
        lotId,
        storageLocationId: locationId,
        onHand: updates.onHand,
        createdAt: new Date(),
        updatedAt: null,
      })
      .execute();
  }
}
