import type { EbayContext } from "./ebayFinancesApi"
import { parseAmount } from "./ebayFinancesApi"
import { buildSigningHeaders } from "./ebaySignature"

/**
 * eBay Sell **Fulfillment** API helper (order listing) for the eBay Bestellabgleich
 * reconciliation page.
 *
 * NB: this is a DIFFERENT host from the Finances API. Finances lives on apiz.ebay.com
 * and MANDATES RFC 9421 digital signatures; the Fulfillment API lives on api.ebay.com
 * and getOrders/getOrder (read calls) do NOT require signatures. We therefore call it
 * UNSIGNED, but fall back to a signed retry if eBay ever answers 403 about a missing
 * signature — so the feature keeps working if eBay extends the mandate to reads.
 *
 * READ-ONLY: nothing here writes to iDempiere or eBay.
 */

export const EBAY_FULFILLMENT_BASE = {
  production: 'https://api.ebay.com',
  sandbox: 'https://api.sandbox.ebay.com'
}

const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))

const looksLikeSignatureError = (status: number, json: any, text: string): boolean => {
  if (status !== 403) return false
  const blob = (JSON.stringify(json || {}) + ' ' + (text || '')).toLowerCase()
  return blob.includes('signature')
}

// Generic Fulfillment-API GET. Tries unsigned first; on a 403 that mentions a
// signature, retries ONCE with RFC 9421 signing headers. Throws an Error carrying
// .status + the eBay message on any other non-2xx.
export const ebayFulfillmentGet = async (ctx: EbayContext, path: string): Promise<any> => {
  const base = ctx.environment === 'sandbox' ? EBAY_FULFILLMENT_BASE.sandbox : EBAY_FULFILLMENT_BASE.production
  const url = `${base}${path}`

  const doFetch = async (extraHeaders: Record<string, string>) => {
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${ctx.accessToken}`,
        'Content-Type': 'application/json',
        // eBay requires a marketplace id header on Fulfillment calls.
        'X-EBAY-C-MARKETPLACE-ID': 'EBAY_DE',
        ...extraHeaders
      }
    })
    const text = await response.text()
    let json: any = {}
    try { json = text ? JSON.parse(text) : {} } catch { json = { raw: text } }
    return { response, json, text }
  }

  let { response, json, text } = await doFetch({})

  // Signature-mandate fallback: retry once, signed.
  if (!response.ok && looksLikeSignatureError(response.status, json, text)) {
    try {
      const signHeaders = await buildSigningHeaders({
        method: 'GET', url, clientId: ctx.clientId, clientSecret: ctx.clientSecret, environment: ctx.environment
      })
      ;({ response, json, text } = await doFetch(signHeaders))
    } catch { /* keep the original 403 below */ }
  }

  if (!response.ok) {
    const msg = json?.errors?.[0]?.message || json?.errors?.[0]?.longMessage ||
      json?.error_description || text || response.statusText
    const err: any = new Error(`eBay API ${response.status}: ${msg}`)
    err.status = response.status
    err.ebay = json
    throw err
  }
  return json
}

export interface EbayOrderRef {
  ebayOrderId: string
  legacyOrderId: string
  creationDate: string
  orderFulfillmentStatus: string
  cancelState: string
  isCanceled: boolean
  status: string            // display token consumed by the page's statusColor()
  total: number             // gross order total (buyer-paid)
  currency: string
}

// Map eBay's fulfillment/cancel status onto the page's colour tokens
// (mirrors the Amazon page's shipped/unshipped/pending/canceled vocabulary).
const displayStatus = (fulfillmentStatus: string, isCanceled: boolean): string => {
  if (isCanceled) return 'canceled'
  switch (String(fulfillmentStatus || '').toUpperCase()) {
    case 'FULFILLED': return 'shipped'
    case 'IN_PROGRESS': return 'partiallyshipped'
    case 'NOT_STARTED': return 'unshipped'
    default: return 'pending'
  }
}

const normalizeOrder = (o: any): EbayOrderRef => {
  const cancelState = String(o?.cancelStatus?.cancelState || '')
  const isCanceled = cancelState.toUpperCase() === 'CANCELED'
  const fulfillment = String(o?.orderFulfillmentStatus || '')
  return {
    ebayOrderId: String(o?.orderId || ''),
    legacyOrderId: String(o?.legacyOrderId || ''),
    creationDate: o?.creationDate || '',
    orderFulfillmentStatus: fulfillment,
    cancelState,
    isCanceled,
    status: displayStatus(fulfillment, isCanceled),
    total: parseAmount(o?.pricingSummary?.total),
    currency: o?.pricingSummary?.total?.currency || 'EUR'
  }
}

// Pull EVERY eBay order created in [createdAfter, createdBefore) via the Fulfillment
// API, paging by offset. eBay's getOrders is far less throttled than Amazon, but we
// still cap pages + wall-clock so a huge full-year query can't time out the proxy.
export const listAllEbayOrders = async (
  ctx: EbayContext,
  opts: { createdAfter: string; createdBefore: string; maxPages?: number; budgetMs?: number; pageDelayMs?: number }
): Promise<{ orders: EbayOrderRef[]; truncated: boolean; throttled: boolean; pages: number }> => {
  const limit = 200
  const maxPages = opts.maxPages ?? 40        // 40 * 200 = 8000 orders (matches the matcher cap)
  const budgetMs = opts.budgetMs ?? 45000
  const pageDelayMs = opts.pageDelayMs ?? 0
  const startedAt = Date.now()

  const orders: EbayOrderRef[] = []
  let offset = 0
  let pages = 0
  let total = Number.POSITIVE_INFINITY
  let truncated = false
  let throttled = false

  // eBay getOrders date filter: creationdate:[start..end] (ISO 8601 UTC, ms precision).
  const filter = `creationdate:[${opts.createdAfter}..${opts.createdBefore}]`

  while (offset < total && pages < maxPages) {
    if (orders.length > 0 && Date.now() - startedAt > budgetMs) { truncated = true; break }

    const path = `/sell/fulfillment/v1/order?limit=${limit}&offset=${offset}&filter=${encodeURIComponent(filter)}`

    let json: any
    try {
      json = await ebayFulfillmentGet(ctx, path)
    } catch (e: any) {
      // Rate-limited → one short backoff, then retry the same page (within budget).
      if (Number(e?.status) === 429) {
        throttled = true
        if (orders.length > 0 && Date.now() - startedAt > budgetMs) { truncated = true; break }
        await sleep(2000)
        try { json = await ebayFulfillmentGet(ctx, path) }
        catch (e2: any) { if (orders.length > 0) { truncated = true; break } throw e2 }
      } else {
        // Nothing collected yet → surface the error; otherwise return the partial set.
        if (orders.length > 0) { truncated = true; break }
        throw e
      }
    }

    const batch = json?.orders || []
    for (const o of batch) orders.push(normalizeOrder(o))
    total = Number.isFinite(json?.total) ? Number(json.total) : (offset + batch.length)
    pages++

    if (!batch.length) break
    offset += limit
    if (pageDelayMs && offset < total && pages < maxPages) await sleep(pageDelayMs)
  }

  if (offset < total && pages >= maxPages) truncated = true

  return { orders, truncated, throttled, pages }
}
