import { Pool } from 'pg'
import refreshTokenHelper from "../../../utils/refreshTokenHelper"

// Cross-dock planning board data: inbound POs + outbound SOs in a date window,
// plus the persisted line-level allocations (CUST_CrossDockAlloc) aggregated
// into per-order-pair "links" with live coverage numbers.
//
// Same direct-Postgres pattern as orders/openso-list + orders/po-upcoming
// (org gate from the logship_role cookie, ad_client 1000000).
//
// Query params:
//   from, to     — ISO dates (inclusive), default today-3d … today+14d
//   warehouseId  — optional m_warehouse filter (applies to both lanes)
//
// Fail-soft: when CUST_CrossDockAlloc doesn't exist yet in iDempiere the lanes
// still load and `linkTableMissing: true` tells the UI to disable linking.
const handleFunc = async (event: any) => {
  const config = useRuntimeConfig()
  const dbConfig = {
    host: config.pgHost,
    port: parseInt(config.pgPort || '5432'),
    database: config.pgDatabase,
    user: config.pgUser,
    password: config.pgPassword,
  }

  if (!dbConfig.host || !dbConfig.database || !dbConfig.user || !dbConfig.password) {
    throw createError({
      statusCode: 500,
      message: 'PostgreSQL credentials not configured. Please set PG_HOST, PG_DATABASE, PG_USER, PG_PASSWORD in .env'
    })
  }

  const query = getQuery(event)
  const isoDate = (v: any, fallbackOffsetDays: number) => {
    const d = v ? new Date(String(v)) : new Date(Date.now() + fallbackOffsetDays * 86400000)
    if (isNaN(d.getTime())) throw createError({ statusCode: 400, message: 'invalid from/to date' })
    return d.toISOString().slice(0, 10)
  }
  const from = isoDate(query?.from, -3)
  const to = isoDate(query?.to, 14)
  const warehouseId = query?.warehouseId ? parseInt(String(query.warehouseId)) : null

  // Limited roles (FrontendMenu == 'c') only see their own organization.
  const roleCookie = getCookie(event, 'logship_role')
  let menuType: string = 'Menu'
  try {
    const role = roleCookie ? JSON.parse(roleCookie) : null
    menuType = role?.FrontendMenu?.id || role?.FrontendMenu || 'Menu'
  } catch {}
  const orgIdCookie = getCookie(event, 'logship_organization_id')
  const orgIdNum = orgIdCookie ? parseInt(orgIdCookie) : NaN
  const orgFilter: number | null = (menuType === 'c' && Number.isFinite(orgIdNum)) ? orgIdNum : null

  const pool = new Pool(dbConfig)

  try {
    // ---- inbound lane: open/completed purchase orders arriving in the window ----
    const inboundSql = `
      SELECT o.c_order_id AS id,
             o.documentno,
             to_char(o.dateordered,  'YYYY-MM-DD') AS dateordered,
             to_char(o.datepromised, 'YYYY-MM-DD') AS datepromised,
             o.grandtotal,
             o.ad_org_id,
             COALESCE(org.name, 'Unknown') AS org_name,
             o.m_warehouse_id,
             wh.name AS warehouse_name,
             o.c_bpartner_id,
             bp.name AS partner_name,
             COALESCE(ol.line_count, 0) AS line_count,
             COALESCE(ol.total_qty, 0)  AS total_qty,
             COALESCE(ol.open_qty, 0)   AS open_qty,
             EXISTS (
               SELECT 1 FROM m_inout io
               WHERE io.c_order_id = o.c_order_id
                 AND io.issotrx = 'N'
                 AND io.docstatus IN ('CO','CL')
             ) AS received
      FROM c_order o
      LEFT JOIN c_bpartner bp  ON bp.c_bpartner_id  = o.c_bpartner_id
      LEFT JOIN ad_org org     ON org.ad_org_id     = o.ad_org_id
      LEFT JOIN m_warehouse wh ON wh.m_warehouse_id = o.m_warehouse_id
      LEFT JOIN (
        SELECT c_order_id,
               COUNT(*) AS line_count,
               SUM(qtyordered) AS total_qty,
               SUM(qtyordered - COALESCE(qtydelivered, 0)) AS open_qty
        FROM c_orderline
        GROUP BY c_order_id
      ) ol ON ol.c_order_id = o.c_order_id
      WHERE o.issotrx = 'N'
        AND o.docstatus = 'CO'
        AND o.ad_client_id = 1000000
        AND o.datepromised >= $1::date
        AND o.datepromised < ($2::date + 1)
        AND ($3::int IS NULL OR o.ad_org_id = $3)
        AND ($4::int IS NULL OR o.m_warehouse_id = $4)
      ORDER BY o.datepromised ASC, o.c_order_id ASC
    `

    // ---- outbound lane: open (unshipped) sales orders promised in the window ----
    // Same universe as orders/openso-list (incl. stock check for is_unfulfillable).
    const outboundSql = `
      WITH stock AS (
        SELECT s.m_product_id,
               l.m_warehouse_id,
               SUM(s.qtyonhand) AS qtyonhand
        FROM m_storage s
        JOIN m_locator l ON l.m_locator_id = s.m_locator_id
        LEFT JOIN m_locatortype lt ON lt.m_locatortype_id = l.m_locatortype_id
        WHERE lt.m_locatortype_id IS NULL
           OR lt.isavailableforshipping = 'Y'
           OR lt.isavailableforreplenishment = 'Y'
           OR lt.isavailableforreservation = 'Y'
        GROUP BY s.m_product_id, l.m_warehouse_id
      )
      SELECT o.c_order_id AS id,
             o.documentno,
             to_char(o.dateordered,  'YYYY-MM-DD') AS dateordered,
             to_char(o.datepromised, 'YYYY-MM-DD') AS datepromised,
             o.grandtotal,
             o.ad_org_id,
             COALESCE(org.name, 'Unknown') AS org_name,
             o.m_warehouse_id,
             wh.name AS warehouse_name,
             o.c_bpartner_id,
             bp.name AS partner_name,
             COALESCE(ol.line_count, 0) AS line_count,
             COALESCE(ol.total_qty, 0)  AS total_qty,
             COALESCE(ol.open_qty, 0)   AS open_qty,
             EXISTS (
               SELECT 1
               FROM c_orderline ol2
               JOIN m_product p ON p.m_product_id = ol2.m_product_id
               LEFT JOIN stock st
                 ON st.m_product_id   = ol2.m_product_id
                AND st.m_warehouse_id = o.m_warehouse_id
               WHERE ol2.c_order_id = o.c_order_id
                 AND p.producttype = 'I'
                 AND (ol2.qtyordered - COALESCE(ol2.qtydelivered, 0)) > 0
                 AND (ol2.qtyordered - COALESCE(ol2.qtydelivered, 0)) > COALESCE(st.qtyonhand, 0)
             ) AS is_unfulfillable
      FROM c_order o
      LEFT JOIN c_bpartner bp  ON bp.c_bpartner_id  = o.c_bpartner_id
      LEFT JOIN ad_org org     ON org.ad_org_id     = o.ad_org_id
      LEFT JOIN m_warehouse wh ON wh.m_warehouse_id = o.m_warehouse_id
      LEFT JOIN (
        SELECT c_order_id,
               COUNT(*) AS line_count,
               SUM(qtyordered) AS total_qty,
               SUM(qtyordered - COALESCE(qtydelivered, 0)) AS open_qty
        FROM c_orderline
        GROUP BY c_order_id
      ) ol ON ol.c_order_id = o.c_order_id
      WHERE o.issotrx = 'Y'
        AND o.docstatus IN ('CO','CL')
        AND o.ad_client_id = 1000000
        AND o.isfulfillmentorder = 'Y'
        AND o.datepromised IS NOT NULL
        AND o.datepromised >= $1::date
        AND o.datepromised < ($2::date + 1)
        AND ($3::int IS NULL OR o.ad_org_id = $3)
        AND ($4::int IS NULL OR o.m_warehouse_id = $4)
        AND EXISTS (
              SELECT 1
              FROM c_orderline ol3
              JOIN m_product p ON p.m_product_id = ol3.m_product_id
              WHERE ol3.c_order_id = o.c_order_id
                AND p.producttype = 'I'
                AND (ol3.qtyordered - COALESCE(ol3.qtydelivered, 0)) > 0
        )
        AND NOT EXISTS (
              SELECT 1
              FROM m_inout io
              JOIN m_inoutline iol ON iol.m_inout_id = io.m_inout_id
              WHERE io.c_order_id = o.c_order_id
                AND io.issotrx = 'Y'
                AND io.docstatus NOT IN ('VO','RE')
        )
      ORDER BY o.datepromised ASC, o.c_order_id ASC
    `

    const params = [from, to, orgFilter, warehouseId]
    const [inboundRes, outboundRes] = await Promise.all([
      pool.query(inboundSql, params),
      pool.query(outboundSql, params)
    ])

    const mapOrder = (r: any) => ({
      id: Number(r.id),
      documentNo: r.documentno,
      dateOrdered: r.dateordered,
      datePromised: r.datepromised,
      grandTotal: parseFloat(r.grandtotal || 0),
      orgId: Number(r.ad_org_id),
      orgName: r.org_name || '',
      warehouseId: r.m_warehouse_id ? Number(r.m_warehouse_id) : null,
      warehouseName: r.warehouse_name || '',
      partnerId: r.c_bpartner_id ? Number(r.c_bpartner_id) : null,
      partnerName: r.partner_name || '',
      lineCount: parseInt(r.line_count || 0),
      totalQty: parseFloat(r.total_qty || 0),
      openQty: parseFloat(r.open_qty || 0),
    })

    const inbound = inboundRes.rows.map((r: any) => ({ ...mapOrder(r), received: !!r.received }))
    const outbound = outboundRes.rows.map((r: any) => ({ ...mapOrder(r), isUnfulfillable: !!r.is_unfulfillable }))

    // ---- links: allocations grouped per PO↔SO pair (fail-soft when table missing) ----
    let links: any[] = []
    let linkTableMissing = false
    // Unqualified + adempiere-schema check: iDempiere tables live in the
    // `adempiere` schema (resolved via search_path), NOT in `public`.
    const reg = await pool.query(
      `SELECT COALESCE(to_regclass('cust_crossdockalloc'), to_regclass('adempiere.cust_crossdockalloc')) AS t`
    )
    if (!reg.rows?.[0]?.t) {
      linkTableMissing = true
    } else {
      const linksSql = `
        WITH alloc AS (
          SELECT a.cust_crossdockalloc_id AS id,
                 a.qtyallocated,
                 pol.c_order_id     AS po_order_id,
                 sol.c_order_id     AS so_order_id,
                 pol.c_orderline_id AS po_line_id,
                 sol.c_orderline_id AS so_line_id,
                 sol.m_product_id,
                 (pol.qtyordered - COALESCE(pol.qtydelivered, 0)) AS po_open,
                 (sol.qtyordered - COALESCE(sol.qtydelivered, 0)) AS so_open
          FROM cust_crossdockalloc a
          JOIN c_orderline pol ON pol.c_orderline_id = a.po_orderline_id
          JOIN c_orderline sol ON sol.c_orderline_id = a.so_orderline_id
          WHERE a.isactive = 'Y'
        ),
        so_open_total AS (
          SELECT ol.c_order_id,
                 SUM(ol.qtyordered - COALESCE(ol.qtydelivered, 0)) AS open_qty
          FROM c_orderline ol
          JOIN m_product p ON p.m_product_id = ol.m_product_id AND p.producttype = 'I'
          GROUP BY ol.c_order_id
        )
        SELECT alloc.po_order_id,
               alloc.so_order_id,
               SUM(alloc.qtyallocated) AS qty_allocated,
               COALESCE(sot.open_qty, 0) AS so_open_total,
               EXISTS (
                 SELECT 1 FROM m_inout io
                 WHERE io.c_order_id = alloc.po_order_id
                   AND io.issotrx = 'N'
                   AND io.docstatus IN ('CO','CL')
               ) AS arrived,
               json_agg(json_build_object(
                 'allocId',       alloc.id,
                 'productId',     alloc.m_product_id,
                 'productName',   p.name,
                 'qtyAllocated',  alloc.qtyallocated,
                 'poOpenQty',     alloc.po_open,
                 'soOpenQty',     alloc.so_open,
                 'poOrderLineId', alloc.po_line_id,
                 'soOrderLineId', alloc.so_line_id
               ) ORDER BY p.name) AS products
        FROM alloc
        LEFT JOIN m_product p ON p.m_product_id = alloc.m_product_id
        LEFT JOIN so_open_total sot ON sot.c_order_id = alloc.so_order_id
        GROUP BY alloc.po_order_id, alloc.so_order_id, sot.open_qty
      `
      const linksRes = await pool.query(linksSql)
      links = linksRes.rows.map((r: any) => {
        const qtyAllocated = parseFloat(r.qty_allocated || 0)
        const soOpenTotal = parseFloat(r.so_open_total || 0)
        return {
          poOrderId: Number(r.po_order_id),
          soOrderId: Number(r.so_order_id),
          qtyAllocated,
          soOpenTotal,
          // SO fully delivered (open 0) with allocations still present → treat as covered.
          coveragePct: soOpenTotal > 0 ? Math.min(100, Math.round(100 * qtyAllocated / soOpenTotal)) : 100,
          arrived: !!r.arrived,
          products: r.products || []
        }
      })
    }

    return { inbound, outbound, links, linkTableMissing, from, to }
  } catch (err: any) {
    console.error('PostgreSQL query error (crossdock board):', err)
    throw createError({ statusCode: 500, message: `Database error: ${err.message}` })
  } finally {
    await pool.end()
  }
}

export default defineEventHandler(async (event) => {
  try {
    return await handleFunc(event)
  } catch (err: any) {
    try {
      await refreshTokenHelper(event)
      return await handleFunc(event)
    } catch (error: any) {
      console.error('Fatal error in crossdock board:', error)
      throw error
    }
  }
})
