import { EntityManager, In } from 'typeorm'
import { DeliveryOrder, OrderInventory, ORDER_INVENTORY_STATUS } from '@things-factory/sales-base'
import { Domain } from '@things-factory/shell'
import { User } from '@things-factory/auth-base'
import { generateInventoryHistory, Inventory, INVENTORY_TRANSACTION_TYPE } from '@things-factory/warehouse-base'
import { WorksheetDetail, WORKSHEET_TYPE, WORKSHEET_STATUS } from '@things-factory/worksheet-base'

export const undoLoad = {
  async undoLoad(_: any, { doId }, context: any) {
    const { domain, tx, user }: { domain: Domain; tx: EntityManager; user: User } = context.state

    const deliveryOrder: DeliveryOrder = await tx.getRepository(DeliveryOrder).findOne({
      where: { id: doId, domain },
      relations: ['orderInventories', 'orderInventories.inventory', 'releaseGood', 'releaseGood.orderInventories']
    })

    if (!deliveryOrder) {
      throw new Error(`Delivery order not found`)
    }

    const releaseGood = deliveryOrder.releaseGood
    const doInventories = deliveryOrder.orderInventories

    if (!doInventories.length) {
      await tx.getRepository(DeliveryOrder).delete(deliveryOrder.id)
      return true
    }

    // OPTIMIZATION 1: Batch fetch all worksheet details in one query instead of N queries
    const doInventoryIds = doInventories.map(oi => oi.id)
    const worksheetDetails = await tx.getRepository(WorksheetDetail).find({
      where: {
        targetInventory: { id: In(doInventoryIds) },
        type: WORKSHEET_TYPE.LOADING,
        status: WORKSHEET_STATUS.DONE
      },
      relations: ['targetInventory']
    })

    // Create a map for O(1) lookup of worksheet details by targetInventory.id
    const wsdByTargetId = new Map<string, WorksheetDetail>()
    worksheetDetails.forEach((wsd: WorksheetDetail) => {
      const targetInvId = (wsd as any).targetInventory?.id
      if (targetInvId) {
        wsdByTargetId.set(targetInvId, wsd)
      }
    })

    // OPTIMIZATION 2: Create a map for O(1) lookup of previous (split) order inventories
    // Key: inventoryId_productId_productDetailId, Value: OrderInventory with LOADING status
    const previousOiMap = new Map<string, OrderInventory>()
    const doInventoryIdSet = new Set(doInventoryIds)
    releaseGood.orderInventories.forEach(roInventory => {
      if (!doInventoryIdSet.has(roInventory.id) && roInventory.status === ORDER_INVENTORY_STATUS.LOADING) {
        const key = `${roInventory.inventoryId}_${roInventory.productId}_${roInventory.productDetailId}`
        previousOiMap.set(key, roInventory)
      }
    })

    // Categorize order inventories for batch processing
    const toTerminate: { doInventory: OrderInventory; previousOi: OrderInventory; wsd: WorksheetDetail }[] = []
    const toRevert: { doInventory: OrderInventory; wsd: WorksheetDetail }[] = []

    for (const doInventory of doInventories) {
      const worksheetDetail = wsdByTargetId.get(doInventory.id)
      const lookupKey = `${doInventory.inventoryId}_${doInventory.productId}_${doInventory.productDetailId}`
      const previousOi = previousOiMap.get(lookupKey)

      if (previousOi) {
        toTerminate.push({ doInventory, previousOi, wsd: worksheetDetail })
      } else {
        toRevert.push({ doInventory, wsd: worksheetDetail })
      }
    }

    // OPTIMIZATION 3: Batch update operations
    const now = new Date()

    // Process terminations (split order inventories)
    if (toTerminate.length > 0) {
      // Update previous OIs with combined quantities - must be done individually due to different values
      await Promise.all(
        toTerminate.map(({ doInventory, previousOi }) =>
          tx.getRepository(OrderInventory).update(previousOi.id, {
            releaseQty: previousOi.releaseQty + doInventory.releaseQty,
            pickedQty: previousOi.pickedQty + doInventory.pickedQty,
            sortedQty: previousOi.sortedQty + doInventory.sortedQty,
            releaseUomValue: previousOi.releaseUomValue + doInventory.releaseUomValue,
            updater: user,
            updatedAt: now
          })
        )
      )

      // Batch terminate the DO inventories
      const terminateIds = toTerminate.map(t => t.doInventory.id)
      await tx.getRepository(OrderInventory).update(
        { id: In(terminateIds) },
        {
          deliveryOrder: null,
          status: ORDER_INVENTORY_STATUS.TERMINATED,
          updatedAt: now,
          updater: user
        }
      )

      // Batch delete worksheet details
      const wsdToDeleteIds = toTerminate.filter(t => t.wsd?.id).map(t => t.wsd.id)
      if (wsdToDeleteIds.length > 0) {
        await tx.getRepository(WorksheetDetail).delete({ id: In(wsdToDeleteIds) })
      }
    }

    // Process reversions (non-split order inventories)
    if (toRevert.length > 0) {
      // Batch update order inventories to LOADING status
      const revertOiIds = toRevert.map(r => r.doInventory.id)
      await tx.getRepository(OrderInventory).update(
        { id: In(revertOiIds) },
        {
          deliveryOrder: null,
          status: ORDER_INVENTORY_STATUS.LOADING,
          loadedAt: null,
          loadedByUser: null,
          loadedBy: null,
          updater: user
        }
      )

      // Batch update worksheet details to EXECUTING status
      const wsdToUpdateIds = toRevert.filter(r => r.wsd?.id).map(r => r.wsd.id)
      if (wsdToUpdateIds.length > 0) {
        await tx.getRepository(WorksheetDetail).update(
          { id: In(wsdToUpdateIds) },
          {
            status: WORKSHEET_STATUS.EXECUTING,
            updater: user,
            updatedAt: now
          }
        )
      }
    }

    // OPTIMIZATION 4: Generate inventory histories in parallel
    await Promise.all(
      doInventories.map(doInventory => {
        const inventory: Inventory = doInventory.inventory
        return generateInventoryHistory(inventory, releaseGood, INVENTORY_TRANSACTION_TYPE.UNDO_LOADING, 0, 0, user, tx)
      })
    )

    await tx.getRepository(DeliveryOrder).delete(deliveryOrder.id)

    return true
  }
}
