import { EntityManager, getRepository, In, Not } from 'typeorm'
import { Domain } from '@things-factory/biz-base'
import { Worksheet, WorksheetDetail, WORKSHEET_TYPE, computeConversionString } from '@things-factory/worksheet-base'
import { Inventories, InventoryChange } from '@things-factory/warehouse-base'
import { OrderInventory, ReleaseGood, ORDER_STATUS, ORDER_INVENTORY_STATUS } from '@things-factory/sales-base'

export const zoneWorksheetOverview = {
  async zoneWorksheetOverview(_: any, { releaseGoods }, context: any) {
    const { domain, tx }: { domain: Domain; tx: EntityManager } = context.state

    let data = await tx.query(
      `
      select 
        b.name as "company", 
        count(distinct oi.product_id) as "noOfSku", 
        count(distinct rg.name) as "noOfRo", 
        l.zone as "zone"
      from order_inventories oi 
      inner join release_goods rg on oi.release_good_id = rg.id 
      inner join inventories i on oi.inventory_id = i.id 
      inner join locations l on i.location_id = l.id 
      inner join products p on oi.product_id = p.id
      inner join bizplaces b on rg.bizplace_id = b.id 
      where rg.name = ANY($1)
        and rg.domain_id = $2
      group by b.name, l.zone
    `,
      [releaseGoods, domain.id]
    )

    let items = data.map(item => {
      return {
        company: item.company,
        noOfSku: parseInt(item.noOfSku, 10),
        noOfRo: parseInt(item.noOfRo, 10),
        zone: item.zone
      }
    })

    return { items }
  }
}

interface ZoneWorksheetInterface {
  worksheet?: Worksheet
  worksheetDetail?: WorksheetDetail[]
  releaseGood?: ReleaseGood[]
  inventoryChanges?: InventoryChange[]
  groupedOrderInventories?: OrderInventory[]
  completable?: String
}
export const zonePickingWorksheet = {
  async zonePickingWorksheet(_: any, { name, locationSortingRules, filter }, context: any) {
    const { domain }: { domain: Domain } = context.state

    let zoneWorksheet: ZoneWorksheetInterface = {}

    //fetch worksheet
    let worksheet: Worksheet = await getRepository(Worksheet).findOne({
      where: {
        domain,
        name
      },
      relations: ['bizplace', 'assignee']
    })

    if (!worksheet) {
      throw new Error('unable to find worksheet')
    }

    zoneWorksheet.worksheet = worksheet

    //fetch worksheet details
    zoneWorksheet.worksheetDetail = await getRepository(WorksheetDetail).find({
      where: { worksheet }
    })

    //fetch order inventories
    let qb = getRepository(OrderInventory)
      .createQueryBuilder('oi')
      .innerJoinAndSelect('oi.product', 'p', 'oi.product_id = p.id')
      .innerJoinAndSelect('oi.inventory', 'i', 'oi.inventory_id = i.id')
      .innerJoinAndSelect('i.location', 'l', 'i.location_id = l.id')
      .leftJoin('oi.releaseGood', 'rg', 'oi.release_good_id = rg.id')
      .leftJoinAndSelect('oi.productDetail', 'pd')
      .leftJoinAndSelect('pd.childProductDetail', 'cpd')
      .leftJoinAndSelect('pd.parentProductDetails', 'ppd')
      .where('ref_worksheet_id = :worksheetId', { worksheetId: worksheet.id })

    //get all oi to find inventory changes and determine if the worksheet is completable
    const orderInventories: OrderInventory[] = await qb.getMany()

    //item assigned to obsolete inventories will not be displayed during picking
    if (filter) {
      qb.andWhere('rg.status != :orderStatus', { orderStatus: ORDER_STATUS.OBSOLETE })
      qb.andWhere('oi.status != :terminateStatus', { terminateStatus: ORDER_INVENTORY_STATUS.TERMINATED })
    }

    if (filter && filter == 'PICKING') {
      qb.andWhere('oi.status = :pickingStatus', { pickingStatus: ORDER_INVENTORY_STATUS.PROCESSING })
    }

    if (filter && filter == 'PICKED') {
      qb.andWhere('oi.status = :pickedStatus', { pickedStatus: ORDER_INVENTORY_STATUS.PICKED })
    }

    if (locationSortingRules?.length > 0) {
      locationSortingRules.forEach((rule: { name: string; desc: boolean }) => {
        qb.addOrderBy(`l.${rule.name}`, rule.desc ? 'DESC' : 'ASC')
      })
    }

    //fetch inventory change for issues on all related inventories
    let invIds = [...new Set(orderInventories.map(oi => oi.inventoryId))]

    zoneWorksheet.inventoryChanges = await getRepository(InventoryChange).find({
      where: {
        inventory: In(invIds),
        transactionType: In(['MISSING', 'EXPIRED']),
        status: In(['APPROVED', 'PENDING'])
      },
      relations: ['inventory', 'inventory.product']
    })

    //fetch ro related to the worksheet
    let roIds = [...new Set(orderInventories.map(item => item.releaseGoodId))]

    zoneWorksheet.releaseGood = await getRepository(ReleaseGood).find({
      where: { id: In(roIds) }
    })

    //get all pickable items in the worksheet
    let items: OrderInventory[] = await qb.getMany()

    //item with pending/approved inventory adjustment will not be displayed during picking
    if (filter) {
      zoneWorksheet.completable = isCompletable(orderInventories, zoneWorksheet.inventoryChanges)
      items = items.filter(oi => !zoneWorksheet.inventoryChanges.map(ic => ic.inventory.id).includes(oi.inventoryId))
    }

    //group oi based on inventory id
    const grouped = items?.reduce((group, oi) => {
      const inventoryId = oi.inventory?.id
      if (inventoryId) {
        if (!group[inventoryId]) {
          group[inventoryId] = {
            product: oi.product,
            inventory: oi.inventory,
            packingType: oi.packingType,
            packingSize: oi.packingSize,
            productDetail: oi.productDetail,
            releaseQty: 0,
            pickedQty: 0,
            releaseUomValue: 0
          }
        }

        //sum up the quantities and uom value
        group[inventoryId].releaseQty += oi.releaseQty || 0
        group[inventoryId].pickedQty += oi.pickedQty || 0
        group[inventoryId].releaseUomValue += oi.releaseUomValue || 0
      }
      return group
    }, {})

    //compute conversion string for each grouped item
    zoneWorksheet.groupedOrderInventories = Object.values(grouped).map((item: any) => ({
      ...item,
      conversion: computeConversionString(item.releaseQty, item.productDetail)
    }))

    return zoneWorksheet
  }
}

function isCompletable(oi: OrderInventory[], ic: InventoryChange[]): String {
  //find processing oi and check if there is any pending inventory adjustment
  let processingOi = oi.filter(item => item.status === ORDER_INVENTORY_STATUS.PROCESSING)

  processingOi = processingOi.filter(
    item => !ic.filter(change => change.status === 'APPROVED').some(change => change.inventory.id === item.inventoryId)
  )

  if (processingOi.length > 0 && oi.some(item => item.status === ORDER_INVENTORY_STATUS.PICKED)) {
    return 'partial'
  } else if (processingOi.length > 0) {
    return 'false'
  } else {
    return 'true'
  }
}
