import { string } from 'alga-js'
import refreshTokenHelper from "../../../utils/refreshTokenHelper"
import forceLogoutHelper from "../../../utils/forceLogoutHelper"
import errorHandlingHelper from "../../../utils/errorHandlingHelper"
import fetchHelper from "../../../utils/fetchHelper"
import {
  getAmazonAccessToken,
  resolveMarketplaceId,
  getReport,
  getReportDocumentMeta,
  downloadReportText,
  parseSettlementFlatFile,
  parseAmazonAmount,
  isSettlementHeaderRow,
  categorizeSettlementRow
} from "../../../utils/amazonSpApi"

// READ-ONLY: matches a set of Amazon order ids to internal c_order records.
// Only GETs orders to show the reference/match indicator — writes nothing.
// Looks up by amazon_order_id first, then ExternalOrderId for any still unmatched.
const matchOrders = async (event: any, token: any, orderIds: string[]) => {
  const map: Record<string, { id: number; documentNo: string }> = {}
  const ids = orderIds.filter(Boolean)
  if (!ids.length) return { map, truncated: false }

  const CAP = 3000
  const truncated = ids.length > CAP
  const work = truncated ? ids.slice(0, CAP) : ids

  const lookup = async (field: string, pending: string[]) => {
    const CHUNK = 45
    for (let i = 0; i < pending.length; i += CHUNK) {
      const chunk = pending.slice(i, i + CHUNK)
      const filter = chunk.map(id => `${field} eq '${String(id).replace(/'/g, "''")}'`).join(' OR ')
      const res: any = await fetchHelper(
        event,
        `models/c_order?$filter=${string.urlEncode(filter)}&$select=${string.urlEncode('C_Order_ID,DocumentNo,amazon_order_id,ExternalOrderId')}&$top=200`,
        'GET',
        token,
        null
      )
      for (const rec of (res?.records || [])) {
        const entry = { id: rec.id, documentNo: rec.DocumentNo || '' }
        // Register under both ids so a hit on either field resolves the order.
        if (rec.amazon_order_id) map[String(rec.amazon_order_id)] = entry
        if (rec.ExternalOrderId) map[String(rec.ExternalOrderId)] = entry
      }
    }
  }

  // Pass 1: amazon_order_id
  await lookup('amazon_order_id', work)
  // Pass 2: ExternalOrderId for whatever is still unmatched
  const stillMissing = work.filter(id => !map[id])
  if (stillMissing.length) await lookup('ExternalOrderId', stillMissing)

  return { map, truncated }
}

type InvoiceRef = { invoiceId: number; documentNo: string; grandTotal: number; docStatus: string }

// For matched orders, find any linked c_invoice so we can show the invoice no/link.
// Prefers a Completed/Closed invoice over a draft when an order has several.
// Mirrors the orders-check endpoint's lookupInvoices.
const lookupInvoices = async (event: any, token: any, internalOrderIds: number[]) => {
  const map: Record<number, InvoiceRef> = {}
  const ids = [...new Set(internalOrderIds.filter(Boolean))]
  if (!ids.length) return map

  const CHUNK = 45
  for (let i = 0; i < ids.length; i += CHUNK) {
    const chunk = ids.slice(i, i + CHUNK)
    // Only live invoices count as "invoice exists" — exclude voided/reversed ones
    // (iDempiere keeps them IsActive=true). Whitelist: DR / CO / CL.
    const filter = chunk
      .map(id => `((C_Order_ID eq ${id}) and (DocStatus eq 'CO' or DocStatus eq 'CL' or DocStatus eq 'DR'))`)
      .join(' OR ')
    const res: any = await fetchHelper(
      event,
      `models/c_invoice?$filter=${string.urlEncode(filter)}&$select=${string.urlEncode('C_Invoice_ID,DocumentNo,GrandTotal,DocStatus,C_Order_ID')}&$top=400`,
      'GET',
      token,
      null
    )
    for (const rec of (res?.records || [])) {
      const oid = rec.C_Order_ID?.id ?? rec.C_Order_ID
      if (oid == null) continue
      const docStatus = rec.DocStatus?.id || rec.DocStatus?.identifier || rec.DocStatus || ''
      const ref: InvoiceRef = {
        invoiceId: rec.id ?? rec.C_Invoice_ID,
        documentNo: rec.DocumentNo || '',
        grandTotal: typeof rec.GrandTotal === 'number' ? rec.GrandTotal : Number(rec.GrandTotal || 0),
        docStatus
      }
      const existing = map[oid]
      const isFinal = (s: string) => {
        const u = String(s || '').toUpperCase()
        return u === 'CO' || u === 'CL' || u.includes('COMPLET') || u.includes('CLOSED')
      }
      if (!existing || (!isFinal(existing.docStatus) && isFinal(docStatus))) {
        map[oid] = ref
      }
    }
  }
  return map
}

const handleFunc = async (event: any, authToken: any = null) => {
  const token = authToken ?? await getTokenHelper(event)
  const query = getQuery(event)
  const orderSourceId = query.orderSourceId
  const reportId = query.reportId ? String(query.reportId) : null
  let reportDocumentId = query.reportDocumentId ? String(query.reportDocumentId) : null

  if (!orderSourceId) return { status: 400, message: 'orderSourceId is required' }
  if (!reportId && !reportDocumentId) return { status: 400, message: 'reportId or reportDocumentId is required' }

  const orderSource: any = await fetchHelper(event, `models/c_ordersource/${orderSourceId}`, 'GET', token, null)
  if (!orderSource) return { status: 404, message: 'Order source not found' }

  const marketplaceIdentifier = String(orderSource?.Marketplace?.identifier || '').toLowerCase()
  const isAmazon = marketplaceIdentifier === 'amazon' || String(orderSource?.Marketplace?.id) === '4' || String(orderSource?.marketplace) === '4'
  if (!isAmazon) return { status: 400, message: `Order source "${orderSource?.Name || orderSourceId}" is not an Amazon marketplace` }

  if (!orderSource?.marketplace_key || !orderSource?.marketplace_secret || !orderSource?.marketplace_token) {
    return { status: 400, message: 'Order source is missing Amazon SP-API credentials' }
  }

  const marketplaceId = resolveMarketplaceId(orderSource?.Marketplace?.identifier, orderSource?.Description)
  if (!marketplaceId) return { status: 400, message: 'Could not resolve an Amazon marketplaceId from the order source' }

  const accessToken = await getAmazonAccessToken(
    orderSource.marketplace_key,
    orderSource.marketplace_secret,
    orderSource.marketplace_token
  )

  // Resolve the document id from the report id when not supplied directly.
  if (!reportDocumentId && reportId) {
    const report = await getReport(accessToken, reportId)
    reportDocumentId = report?.reportDocumentId || null
    if (!reportDocumentId) {
      return { status: 404, message: `Report ${reportId} has no document yet (processingStatus: ${report?.processingStatus || 'unknown'})` }
    }
  }

  const docMeta = await getReportDocumentMeta(accessToken, reportDocumentId as string)
  const text = await downloadReportText(docMeta.url, docMeta.compressionAlgorithm)
  const rawRows = parseSettlementFlatFile(text)

  // Settlement metadata (header/summary row carries the payout total + deposit date).
  const headerRow = rawRows.find(isSettlementHeaderRow)
  const anyRow = rawRows[0] || {}
  const settlement = {
    settlementId: headerRow?.['settlement-id'] || anyRow['settlement-id'] || '',
    periodStart: headerRow?.['settlement-start-date'] || anyRow['settlement-start-date'] || '',
    periodEnd: headerRow?.['settlement-end-date'] || anyRow['settlement-end-date'] || '',
    depositDate: headerRow?.['deposit-date'] || anyRow['deposit-date'] || '',
    totalAmount: parseAmazonAmount(headerRow?.['total-amount'] ?? anyRow['total-amount']),
    currency: headerRow?.currency || anyRow.currency || 'EUR'
  }

  // Detail rows (exclude the summary header row).
  const detailRows = rawRows
    .filter(r => !isSettlementHeaderRow(r))
    .map(r => ({
      category: categorizeSettlementRow(r),
      transactionType: r['transaction-type'] || '',
      amountType: r['amount-type'] || '',
      amountDescription: r['amount-description'] || '',
      amount: parseAmazonAmount(r.amount),
      orderId: r['order-id'] || '',
      merchantOrderId: r['merchant-order-id'] || '',
      sku: r.sku || '',
      qty: r['quantity-purchased'] ? Number(r['quantity-purchased']) : null,
      postedDate: r['posted-date'] || r['posted-date-time'] || '',
      marketplace: r['marketplace-name'] || ''
    }))

  // READ-ONLY match to internal orders (best-effort; never blocks the view).
  const distinctOrderIds = [...new Set(detailRows.map(r => r.orderId).filter(Boolean))]
  let matchTruncated = false
  let matchError: string | null = null
  let matchedCount = 0
  try {
    const { map, truncated } = await matchOrders(event, token, distinctOrderIds)
    matchTruncated = truncated
    for (const row of detailRows) {
      const hit = row.orderId ? map[row.orderId] : null
      ;(row as any).internalOrderId = hit?.id ?? null
      ;(row as any).internalDocumentNo = hit?.documentNo ?? ''
      ;(row as any).matched = !!hit
    }
    matchedCount = distinctOrderIds.filter(id => map[id]).length

    // Best-effort: resolve a linked invoice for every matched order so the grid
    // can show a Rechnung column (never blocks the view if it fails).
    try {
      const matchedInternalIds = [...new Set(
        detailRows.map((r: any) => r.internalOrderId).filter(Boolean)
      )] as number[]
      const invoiceMap = await lookupInvoices(event, token, matchedInternalIds)
      for (const row of detailRows) {
        const inv = (row as any).internalOrderId ? invoiceMap[(row as any).internalOrderId] : null
        ;(row as any).invoiceExists = !!inv
        ;(row as any).invoiceId = inv?.invoiceId || null
        ;(row as any).invoiceDocumentNo = inv?.documentNo || ''
        ;(row as any).invoiceTotal = inv?.grandTotal ?? null
        ;(row as any).invoiceDocStatus = inv?.docStatus || ''
      }
    } catch {
      for (const row of detailRows) {
        ;(row as any).invoiceExists = false
        ;(row as any).invoiceId = null
        ;(row as any).invoiceDocumentNo = ''
      }
    }
  } catch (e: any) {
    matchError = e?.message || 'Order matching failed'
    for (const row of detailRows) {
      ;(row as any).internalOrderId = null
      ;(row as any).internalDocumentNo = ''
      ;(row as any).matched = false
      ;(row as any).invoiceExists = false
      ;(row as any).invoiceId = null
      ;(row as any).invoiceDocumentNo = ''
    }
  }

  // Category sums + reconciliation (sum of ALL detail amounts should ≈ payout total).
  const categorySums: Record<string, number> = {
    umsaetze: 0, retouren: 0, amazonFees: 0, fbaFees: 0, werbekosten: 0, sonstiges: 0
  }
  let detailSum = 0
  for (const r of detailRows) {
    detailSum += r.amount
    if (categorySums[r.category] !== undefined) categorySums[r.category] += r.amount
  }

  const summary = {
    ...categorySums,
    auszahlung: settlement.totalAmount,
    lineCount: detailRows.length,
    ordersWithId: distinctOrderIds.length,
    matchedOrders: matchedCount,
    unmatchedOrders: distinctOrderIds.length - matchedCount,
    matchTruncated,
    matchError,
    reconciliation: {
      detailSum: Math.round(detailSum * 100) / 100,
      payout: settlement.totalAmount,
      difference: Math.round((settlement.totalAmount - detailSum) * 100) / 100
    }
  }

  return { status: 200, settlement, summary, rows: detailRows }
}

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

  try {
    data = await handleFunc(event)
  } catch (err: any) {
    // Surface SP-API errors (status-carrying) verbatim instead of treating them
    // as an iDempiere auth failure.
    if (err?.status && err?.message) {
      return { status: err.status, message: err.message, spapi: err.spapi }
    }
    try {
      let authToken: any = await refreshTokenHelper(event)
      data = await handleFunc(event, authToken)
    } catch (error: any) {
      data = errorHandlingHelper(err?.data ?? err, error?.data ?? error)
      forceLogoutHelper(event, data)
    }
  }

  return data
})
