import { EntityManager } from 'typeorm'
import { User } from '@things-factory/auth-base'
import { InventoryCheck } from '@things-factory/sales-base'
import { ListParam, Domain } from '@things-factory/shell'

export const cycleCountReport = {
  async cycleCountReport(_: any, { filters, name }: { filters: any[]; name: string }, context: any) {
    try {
      const { domain, tx }: { domain: Domain; tx: EntityManager } = context.state

      const batchNoVarianceFilter = filters?.find(e => e.name === 'batchNoVariance')
      const expirationDateVarianceFilter = filters?.find(e => e.name === 'expirationDateVariance')
      const locationVarianceFilter = filters?.find(e => e.name === 'locationVariance')

      const cycleCount: InventoryCheck = await tx.getRepository(InventoryCheck).findOne({
        where: { domain, name },
        relations: ['domain']
      })

      await tx.query(`CREATE EXTENSION IF NOT EXISTS tablefunc`)

      // Step 1: Create the temp_fixed_columns table
      await tx.query(
        `
        CREATE TEMP TABLE temp_fixed_columns ON COMMIT DROP AS (
          SELECT 
            d.id as "partner_domain_id",
            w.check_count as "check_count",
            ic.id as "inventory_check_id",
            ici.inventory_id as "inventory_id",
            ic.name as "name",
            ici.product_id as "product_id",
            ici.count_no as "count_no",
            ici.product_detail_id as "product_detail_id", 
            ici.status as "invCheckItemStatus",
            b.name as "company",
            ic.name as "cycle_count_no",
            ic.execution_date as "execution_date",
            ic.status as "status",
            i.carton_id as "carton_id", 
            i.pallet_id as "pallet_id", 
            i.packing_type as "i_packing_type",
            i.uom as "i_uom",
            p.sku as "product_sku", 
            p.name as "product_name", 
            p.brand as "product_brand", 
            p.description as "product_description",
            pd.packing_type as "pd_packing_type",
            pd.uom as "pd_uom",
            ici.origin_batch_no as "origin_batch_no", 
            ici.inspected_batch_no as "inspected_batch_no", 
            ici.origin_uom_value as "origin_uom_value",
            ici.origin_location_id as "origin_location_id", 
            ici.origin_expiration_date as "origin_expiration_date",
            ici.inspected_expiration_date as "inspected_expiration_date",
            l.name as "origin_location_name", 
            ici.inspected_location_id as "inspected_location_id",
            l2.name as "inspected_location_name",
            COALESCE(ici.origin_qty, 0) as "origin_qty", 
            COALESCE(ici.inspected_qty, 0) as "inspected_qty",
            COALESCE(ici.inspected_uom_value, 0) as "inspected_uom_value"
          FROM 
            inventory_checks ic
          INNER JOIN
            inventory_check_items ici on ici.inventory_check_id = ic.id 
          LEFT JOIN
            inventories i on i.id = ici.inventory_id 
          INNER JOIN
            products p ON p.id = ici.product_id 
          INNER JOIN
            product_details pd ON pd.id = ici.product_detail_id
          INNER JOIN 
            worksheets w on w.inventory_check_id = ic.id 
          INNER JOIN 
            worksheet_details wd on wd.worksheet_id = w.id and wd.target_inventory_check_item_id = ici.id 
          LEFT JOIN
            locations l on l.id = ici.origin_location_id
          LEFT JOIN
            locations l2 on l2.id = ici.inspected_location_id
          INNER JOIN
            bizplaces b on b.id = ic.bizplace_id
          INNER JOIN
            domains d on d.id = b.domain_id
          WHERE 
            ic.id = $1
        )`,
        [cycleCount.id]
      )

      // Step 2: Create the temp_dynamic_columns table
      await tx.query(
        `
        CREATE TEMP TABLE temp_dynamic_columns ON COMMIT DROP AS (
          SELECT ic.id as "inventory_check_id",
                w.check_count as "check_count"
          FROM inventory_checks ic
          INNER JOIN worksheets w ON ic.id = w.inventory_check_id
          WHERE ic.id = $1
        )`,
        [cycleCount.id]
      )

      // Step 3: Generate definedCrossColumn for dynamic columns
      const definedCrossColumn = await tx.query(`
        SELECT string_agg(DISTINCT 'count ' || check_count::text, ',') as count_columns,
               string_agg(DISTINCT 'uom count ' || check_count::text, ',') as uom_columns
        FROM temp_dynamic_columns
      `)

      const countColumns = definedCrossColumn[0].count_columns
        .split(',')
        .map(column => `"${column.trim()}" varchar`)
        .join(', ')

      const uomColumns = definedCrossColumn[0].uom_columns
        .split(',')
        .map(column => `"${column.trim()}" varchar`)
        .join(', ')

      // Step 4: Pivot data and create the pivot tables
      await tx.query(`
        CREATE TEMP TABLE temp_pivoted_data ON COMMIT DROP AS (
          SELECT *
          FROM crosstab(
            $$
              SELECT inventory_id, 'count ' || check_count, inspected_qty 
              FROM temp_fixed_columns ORDER BY 1,2,3
            $$,
            $$
              SELECT DISTINCT 'count ' || check_count FROM temp_dynamic_columns ORDER BY 1
            $$
          ) AS ct (inventory_id varchar, ${countColumns})
        )
      `)

      await tx.query(`
        CREATE TEMP TABLE temp_pivoted_uom_data ON COMMIT DROP AS (
          SELECT *
          FROM crosstab(
            $$
              SELECT inventory_id, 'uom count ' || check_count, inspected_uom_value
              FROM temp_fixed_columns ORDER BY 1,2,3
            $$,
            $$
              SELECT DISTINCT 'uom count ' || check_count FROM temp_dynamic_columns ORDER BY 1
            $$
          ) AS ct (inventory_id varchar, ${uomColumns})
        )
      `)

      // Step 5: Create the temp_final_count table
      await tx.query(
        `
        CREATE TEMP TABLE temp_final_count ON COMMIT DROP AS (
          SELECT
            inventory_id,
            count_no,
            MAX(inspected_qty) as "final_count",
            MAX(inspected_uom_value) as "final_uom"
          FROM
            inventory_check_items ici
          WHERE
            inventory_check_id = $1
          GROUP BY inventory_id, count_no
        )`,
        [cycleCount.id]
      )

      // Step 6: Create the final temp table with combined data
      const jsonColumnNames = definedCrossColumn[0].count_columns
        .split(',')
        .map(column => `p."${column.trim()}" as "${column.trim()}"`)
        .join(',')

      const uomJsonColumnNames = definedCrossColumn[0].uom_columns
        .split(',')
        .map(column => `puom."${column.trim()}" as "${column.trim()}"`)
        .join(',')

      await tx.query(`
        CREATE TEMP TABLE temp_final_data ON COMMIT DROP AS (
          SELECT 
            f.*,
            fc.final_count,
            fc.final_uom,
            first_count.origin_qty as "system_qty",
            first_count.origin_batch_no as "system_batch_no",
            first_count.origin_location_name as "system_location_name",
            first_count.origin_uom_value as "system_uom_value",
            row_to_json((SELECT cn FROM (SELECT ${jsonColumnNames}) cn))::varchar as "count_data",
            row_to_json((SELECT cn FROM (SELECT ${uomJsonColumnNames}) cn))::varchar as "count_uom_data",
            ROUND(CAST(fc.final_count - first_count.origin_qty AS NUMERIC), 3) as "count_variance",
            CASE
              WHEN (COALESCE(first_count.origin_batch_no, '') <> COALESCE(f.inspected_batch_no, ''))
              THEN true
              ELSE false
            END AS "BatchNoVariance",
            CASE
              WHEN (COALESCE(first_count.origin_location_id::text, '') <> COALESCE(f.inspected_location_id::text, ''))
              THEN true
              ELSE false
            END AS "LocationVariance",
            CASE
              WHEN (COALESCE(first_count.origin_uom_value, 0) <> COALESCE(f.inspected_uom_value, 0))
              THEN true
              ELSE false
            END AS "UomValueVariance",
            CASE
              WHEN (COALESCE(first_count.origin_expiration_date, '1900-01-01') <> COALESCE(f.inspected_expiration_date, '1900-01-01'))
              THEN true
              ELSE false
            END AS "ExpirationDateVariance"  
          FROM temp_fixed_columns f
          INNER JOIN (
              SELECT 
                  inventory_id, 
                  count_no, 
                  origin_qty,
                  origin_batch_no, 
                  origin_location_id, 
                  origin_location_name,
                  origin_uom_value,
                  origin_expiration_date
              FROM temp_fixed_columns 
              WHERE count_no = 1
          ) first_count ON f.inventory_id = first_count.inventory_id
          INNER JOIN temp_pivoted_data p ON f.inventory_id::uuid = p.inventory_id::uuid
          INNER JOIN temp_pivoted_uom_data puom ON f.inventory_id::uuid = puom.inventory_id::uuid
          INNER JOIN temp_final_count fc ON f.inventory_id = fc.inventory_id and f.count_no = fc.count_no
        )
      `)

      const whereConditions = []

      if (batchNoVarianceFilter?.value === true) {
        whereConditions.push('"BatchNoVariance" = true')
      }
      if (expirationDateVarianceFilter?.value === true) {
        whereConditions.push('"ExpirationDateVariance" = true')
      }
      if (locationVarianceFilter?.value === true) {
        whereConditions.push('"LocationVariance" = true')
      }

      // Step 7: Select final result
      const result: any = await tx.query(`
        SELECT * FROM temp_final_data
        ${whereConditions.length > 0 ? 'WHERE ' + whereConditions.join(' OR ') : ''}
        `)

      // Step 8: Group inventory id and prepare final response
      const inventoryIds = new Set()
      const items = []
      result.forEach(itm => {
        if (!inventoryIds.has(itm.inventory_id)) {
          inventoryIds.add(itm.inventory_id)

          let countStatus

          if (itm.final_count === itm.system_qty) {
            countStatus = 'HIT'
          } else if (itm.final_count < itm.system_qty) {
            countStatus = 'SHORT'
          } else {
            countStatus = 'EXCESS'
          }

          let data = {
            inventoryCheck: {
              name: itm.name,
              executionDate: itm.execution_date,
              status: itm.status
            },
            inventoryCheckItem: {
              status: itm.invCheckItemStatus,
              originBatchNo: itm.origin_batch_no,
              inspectedBatchNo: itm.inspected_batch_no,
              originUomValue: itm.origin_uom_value,
              inspectedUomValue: itm.inspected_uom_value,
              originQty: itm.origin_qty,
              inspectedQty: itm.inspected_qty,
              originLocation: {
                name: itm.origin_location_name
              },
              inspectedLocation: {
                name: itm.inspected_location_name
              }
            },
            inventory: {
              id: itm.inventory_id,
              cartonId: itm.carton_id,
              palletId: itm.pallet_id,
              packingType: itm.i_packing_type,
              uom: itm.i_uom
            },
            product: {
              sku: itm.product_sku,
              name: itm.product_name,
              brand: itm.product_brand,
              description: itm.product_description
            },
            productDetail: {
              packingType: itm.pd_packing_type,
              uom: itm.pd_uom
            },
            systemQty: itm.system_qty,
            systemBatchNo: itm.system_batch_no,
            systemLocationName: itm.system_location_name,
            systemUomValue: itm.system_uom_value,
            originLocationId: itm.origin_location_id,
            inspectedLocationId: itm.inspected_location_id,
            countData: itm.count_data,
            countUomData: itm.count_uom_data,
            batchNoVariance: itm.BatchNoVariance,
            locationVariance: itm.LocationVariance,
            uomValueVariance: itm.UomValueVariance,
            expirationDateVariance: itm.ExpirationDateVariance,
            finalCount: itm.final_count,
            finalUom: itm.final_uom,
            countVariance: itm.count_variance,
            countStatus,
            company: itm.company,
            partnerDomainId: itm.partner_domain_id
          }
          items.push(data)
        }
      })

      // Step 9: Count total and return the final object
      const finalTotal = inventoryIds.size
      return {
        items,
        total: finalTotal
      }
    } catch (error) {
      throw error
    }
  }
}
