import { string } from 'alga-js'
import refreshTokenHelper from '../../utils/refreshTokenHelper'
import errorHandlingHelper from '../../utils/errorHandlingHelper'
import fetchHelper from '../../utils/fetchHelper'

// Module-level cache: a location's country effectively never changes, so resolving it
// once and reusing it keeps month/year/org navigation fast across requests.
const countryByLocation: Record<number, string> = {}

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const query = getQuery(event)
  const role = getCookie(event, 'logship_role')

  const newUserRole = JSON.parse(role ?? '{}')
  const organizationId = getCookie(event, 'logship_organization_id')
  const orgIdParam = query?.org_id as string

  // Role gate: non client-administrators are locked to their current org.
  const canFilterOrg = !!newUserRole?.IsClientAdministrator
  let effectiveOrgId = ''
  let orgFilter = ''
  if (!canFilterOrg) {
    effectiveOrgId = String(orgIdParam || organizationId || '')
    if (effectiveOrgId) orgFilter = ` AND AD_Org_ID eq ${effectiveOrgId}`
  } else if (orgIdParam) {
    effectiveOrgId = String(orgIdParam)
    orgFilter = ` AND AD_Org_ID eq ${effectiveOrgId}`
  }

  // Selected year + month (default: current month) — scoping to a single month keeps
  // the fetched volume small (~1/12 of a year, less for the running month).
  const now = new Date()
  const selectedYear = query?.year ? Number(query.year) : now.getFullYear()
  let selectedMonth = query?.month ? Number(query.month) : now.getMonth() + 1 // 1-12
  if (selectedMonth < 1 || selectedMonth > 12) selectedMonth = now.getMonth() + 1

  const daysInMonth = new Date(selectedYear, selectedMonth, 0).getDate()
  const mm = String(selectedMonth).padStart(2, '0')
  const monthStart = `${selectedYear}-${mm}-01 00:00:00`
  const monthEnd = `${selectedYear}-${mm}-${String(daysInMonth).padStart(2, '0')} 23:59:59`

  // Only completed, commissioned customer shipments (C-) of fulfillment orders.
  // isFulfillmentOrder lives on the linked order, so it is filtered in JS after a
  // master $select-expand (iDempiere only allows $select inside a master expand —
  // a nested $expand is rejected with "Expanding a master only support the $select…").
  const filter = string.urlEncode(
    `IsCommissioned eq true AND DocStatus eq 'CO' AND IsSOTrx eq true AND MovementType eq 'C-'` +
    ` AND shipping_date ge '${monthStart}' AND shipping_date le '${monthEnd}'${orgFilter}`
  )
  // Master $select-expands only: the order's fulfillment flag, and the ship-to
  // bpartner-location's c_location id (the country is resolved in a second pass).
  const select = 'M_InOut_ID,AD_Org_ID,shipping_date,MovementDate,C_Order_ID,C_BPartner_Location_ID'
  const expand = 'C_Order_ID($select=IsFulfillmentOrder),C_BPartner_Location_ID($select=C_Location_ID)'

  // ---- Pass 1: paginate the qualifying shipments for the month ----
  const pageSize = 1000
  let skip = 0
  const kept: { day: number; org: string; locationId: number | null }[] = []
  const neededLocations = new Set<number>()

  while (true) {
    const res: any = await fetchHelper(
      event,
      `models/m_inout?$filter=${filter}&$select=${select}&$expand=${expand}` +
        `&$orderby=${string.urlEncode('m_inout_id desc')}&$top=${pageSize}&$skip=${skip}`,
      'GET',
      token,
      null
    )
    const records = res?.records || []

    records.forEach((rec: any) => {
      // Fulfillment order only (tolerant casing per iDempiere read rule).
      const ff = rec?.C_Order_ID?.isFulfillmentOrder ?? rec?.C_Order_ID?.IsFulfillmentOrder
      if (ff !== true) return

      // Day-of-month from the shipping date (string-parsed to avoid timezone drift).
      const ds = String(rec?.shipping_date || rec?.MovementDate || '')
      const m = ds.match(/^(\d{4})-(\d{2})-(\d{2})/)
      if (!m) return
      const day = Number(m[3])
      if (day < 1 || day > 31) return

      const org = rec?.AD_Org_ID?.identifier || `Org-${rec?.AD_Org_ID?.id ?? '?'}`
      const locationId = rec?.C_BPartner_Location_ID?.C_Location_ID?.id ?? null
      if (locationId != null && countryByLocation[locationId] === undefined) {
        neededLocations.add(locationId)
      }
      kept.push({ day, org, locationId })
    })

    if (records.length < pageSize) break
    skip += pageSize
    if (skip > 100000) break // safety cap
  }

  // ---- Pass 2: resolve uncached location ids -> country (c_location default
  // C_Country_ID FK already carries the country name as its identifier) ----
  const ids = Array.from(neededLocations)
  const batchSize = 200
  for (let i = 0; i < ids.length; i += batchSize) {
    const batch = ids.slice(i, i + batchSize)
    const locFilter = string.urlEncode(`C_Location_ID in (${batch.join(',')})`)
    const res: any = await fetchHelper(
      event,
      `models/c_location?$filter=${locFilter}&$select=C_Location_ID,C_Country_ID&$top=${batchSize}`,
      'GET',
      token,
      null
    )
    ;(res?.records || []).forEach((loc: any) => {
      const lid = loc?.id ?? loc?.C_Location_ID
      if (lid == null) return
      countryByLocation[lid] = loc?.C_Country_ID?.identifier || 'Unknown'
    })
    // Any id the batch didn't return (shouldn't happen) -> mark Unknown so we don't refetch.
    batch.forEach((lid) => { if (countryByLocation[lid] === undefined) countryByLocation[lid] = 'Unknown' })
  }

  // ---- Aggregate to (day, org, country) cells — the client derives every view
  // (per-country chart, per-org drill-down, the breakdown list) from these. ----
  const countrySet = new Set<string>()
  const orgSet = new Set<string>()
  // tree[day][org][country] = qty  (nested to avoid delimiter collisions in names)
  const tree: Record<number, Record<string, Record<string, number>>> = {}
  const countryTotals: Record<string, number> = {}
  let monthTotal = 0

  kept.forEach(({ day, org, locationId }) => {
    const country = (locationId != null && countryByLocation[locationId]) || 'Unknown'
    countrySet.add(country)
    orgSet.add(org)

    if (!tree[day]) tree[day] = {}
    if (!tree[day][org]) tree[day][org] = {}
    tree[day][org][country] = (tree[day][org][country] || 0) + 1

    countryTotals[country] = (countryTotals[country] || 0) + 1
    monthTotal += 1
  })

  const cells: { day: number; org: string; country: string; qty: number }[] = []
  for (const dayStr of Object.keys(tree)) {
    const day = Number(dayStr)
    for (const org of Object.keys(tree[day])) {
      for (const country of Object.keys(tree[day][org])) {
        cells.push({ day, org, country, qty: tree[day][org][country] })
      }
    }
  }

  const countries = Array.from(countrySet).sort((a, b) => a.localeCompare(b))
  const orgs = Array.from(orgSet).sort((a, b) => a.localeCompare(b))

  return {
    year: selectedYear,
    month: selectedMonth,
    daysInMonth,
    canFilterOrg,
    selectedOrgId: effectiveOrgId,
    countries,
    orgs,
    cells,
    countryTotals,
    monthTotal,
  }
}

export default defineEventHandler(async (event) => {
  let data: any = {}

  try {
    data = await handleFunc(event)
  } catch (err: any) {
    try {
      const authToken: any = await refreshTokenHelper(event)
      data = await handleFunc(event, authToken)
    } catch (error: any) {
      data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
    }
  }

  return data
})
