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,
  listFinancialEventsForGroup,
  flattenFinancialEvents
} from "../../../utils/amazonSpApi"
import { withOptionalCsvExportCols, readCsvExportMark, isCsvExportColumnsAvailable } from "../../../utils/lexofficeCsvExportMark"

// 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.
type OrderRef = { id: number; documentNo: string; csvExported: boolean; csvExportDate: string }

const matchOrders = async (event: any, token: any, orderIds: string[]) => {
  const map: Record<string, OrderRef> = {}
  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 ')
      // The lexoffice CSV-export marker columns are optional (fail-soft until
      // they exist on C_Order — see server/utils/lexofficeCsvExportMark.ts).
      const res: any = await withOptionalCsvExportCols((extraCols) => fetchHelper(
        event,
        `models/c_order?$filter=${string.urlEncode(filter)}&$select=${string.urlEncode(['C_Order_ID', 'DocumentNo', 'amazon_order_id', 'ExternalOrderId', ...extraCols].join(','))}&$top=200`,
        'GET',
        token,
        null
      ))
      for (const rec of (res?.records || [])) {
        const csv = readCsvExportMark(rec)
        const entry: OrderRef = { id: rec.id, documentNo: rec.DocumentNo || '', csvExported: csv.exported, csvExportDate: csv.date }
        // 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
  lexofficeUploaded: boolean
  lexofficeUploadDate: 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,isUploadToLexoffice,UploadDateLexoffice')}&$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,
        // Lexoffice upload status of the invoice (same columns /sales/invoices shows)
        lexofficeUploaded: rec.isUploadToLexoffice === true || rec.isUploadToLexoffice === 'Y' || rec.IsUploadToLexoffice === true || rec.IsUploadToLexoffice === 'Y',
        lexofficeUploadDate: rec.UploadDateLexoffice || rec.uploadDateLexoffice || ''
      }
      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
  // `reportId` holds the Finances API's FinancialEventGroupId (see settlements.get.ts).
  const groupId = query.reportId ? String(query.reportId) : null

  if (!orderSourceId) return { status: 400, message: 'orderSourceId is required' }
  if (!groupId) return { status: 400, message: 'reportId 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 accessToken = await getAmazonAccessToken(
    orderSource.marketplace_key,
    orderSource.marketplace_secret,
    orderSource.marketplace_token
  )

  const pages = await listFinancialEventsForGroup(accessToken, groupId)
  const detailRows = flattenFinancialEvents(pages)

  // The Finances API has no "get one group by id" endpoint (only the list used
  // by settlements.get.ts), so the period + payout total are passed through by
  // the frontend from that same list response rather than looked up again here.
  // This isn't a trust issue: the reconciliation check below independently sums
  // the actual line items fetched above and compares them against this figure —
  // any mismatch (tampered, stale, or otherwise wrong) surfaces there exactly
  // like a real Amazon-side discrepancy would.
  const settlement = {
    settlementId: groupId,
    periodStart: query.periodStart ? String(query.periodStart) : '',
    periodEnd: query.periodEnd ? String(query.periodEnd) : '',
    depositDate: query.fundTransferDate ? String(query.fundTransferDate) : '',
    totalAmount: query.totalAmount != null && query.totalAmount !== '' ? Number(query.totalAmount) : 0,
    currency: query.currency ? String(query.currency) : 'EUR'
  }

  // 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
      ;(row as any).csvExported = !!hit?.csvExported
      ;(row as any).csvExportDate = hit?.csvExportDate || ''
    }
    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 || ''
        ;(row as any).invoiceLexofficeUploaded = !!inv?.lexofficeUploaded
        ;(row as any).invoiceLexofficeDate = inv?.lexofficeUploadDate || ''
      }
    } catch {
      for (const row of detailRows) {
        ;(row as any).invoiceExists = false
        ;(row as any).invoiceId = null
        ;(row as any).invoiceDocumentNo = ''
        ;(row as any).invoiceLexofficeUploaded = false
        ;(row as any).invoiceLexofficeDate = ''
      }
    }
  } 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 = ''
      ;(row as any).invoiceLexofficeUploaded = false
      ;(row as any).invoiceLexofficeDate = ''
      ;(row as any).csvExported = false
      ;(row as any).csvExportDate = ''
    }
  }

  // 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
    }
  }

  // csvExportAvailable: whether C_Order carries the lexoffice CSV-export marker
  // columns (frontend hides the marker/filters/reset until they exist).
  return { status: 200, settlement, summary, rows: detailRows, csvExportAvailable: isCsvExportColumnsAvailable() }
}

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
})
