/**
 * Initial population of CUST_MarketplaceListing from what is already listed on
 * the marketplace ("Import from Amazon"). Preview = adapter.importRemote →
 * matchListingsToProducts (SKU → ASIN tiers, internal price / qty) → rows flagged
 * with the already-imported listing id; Import = upsert of the selected rows.
 *
 * Desired values (Price / Qty) are initialised from the remote values on first
 * import and never overwritten later; a re-import only refreshes Remote* fields
 * (and fills Price/Qty when they are still empty and nothing is queued).
 */
import { matchListingsToProducts, type SisRow } from '../sisMatching'
import { getListingAdapter, marketplaceKeyOf, supportsListings } from './adapters'
import type { AdapterCtx, RemoteListing } from './adapters/types'
import {
  type IdempiereFetch, type ListingRow, type ListingStatus, listListings, insertListing, updateListing, restDate, enc, esc
} from './listingStore'
import { amazonConnectionFromOrderSource, amazonDomainForOrderSource } from '../amazonSpApi'
import { createProductForListing, importAmazonImageIfMissing, importImageFromUrlIfMissing, loadProductForImage, type StrapiProductRef } from './productCreator'

export interface ImportPreviewRow extends SisRow {
  listingKey: string
  externalProductId: string | null
  externalInventoryId: string | null
  imageUrl: string | null
  meta: any
  currency: string | null
  existingListingId: number | null
  existingSyncStatus: string | null
  existingPrice: number | null
  existingQty: number | null
}

export type ImportPreview =
  | { pending: true; startedAt: number }
  | { pending: false; rows: ImportPreviewRow[]; generatedAt: number; fromCache: boolean; marketplace: string; marketplaceDomain: string; counts: { total: number; matched: number; unmatched: number; alreadyImported: number; fba: number } }

export interface ImportRowInput {
  listingKey: string
  sellerSku: string
  asin?: string | null
  amazonItemName?: string | null
  amazonPrice?: number | null
  amazonQty?: number | null
  fulfillmentChannel?: string | null
  status?: string | null
  openDate?: string | null
  currency?: string | null
  productId?: number | null
  productName?: string | null
  /** Marketplace ids (Shopify: product GID / inventoryItem GID); Amazon rows carry the ASIN in `asin`. */
  externalProductId?: string | null
  externalInventoryId?: string | null
  /** Picture on the marketplace (Shopify CDN) — used by importImages for non-Amazon sources. */
  imageUrl?: string | null
  meta?: any
}

export interface ImportResult {
  created: number; updated: number; skipped: number
  productsCreated: number
  images: { imported: number; hasImage: number; noAmazonImage: number; failed: number }
  warnings: string[]
  errors: { listingKey: string; error: string }[]
}

export interface ImportOptions {
  overwriteExisting?: boolean
  /** Create a minimal M_Product (Value = SKU, asin, Amazon price) for rows without a matched product. */
  createProducts?: boolean
  /** Copy the ASIN's MAIN catalog image to the product — only when the product has no image in Strapi. */
  importImages?: boolean
  /** Price list that receives the Amazon price of created products (default: config.public.pricelistid). */
  priceListId?: string | number | null
  salesRepId?: number | null
}

const makeCtx = (f: IdempiereFetch, orderSource: any): AdapterCtx => ({
  orderSource, orderSourceId: Number(orderSource.id), orgId: Number(orderSource?.AD_Org_ID?.id) || 0,
  marketplace: marketplaceKeyOf(orderSource), fetch: f,
  log: (msg, extra) => extra ? console.log(`[listing-import] [${orderSource?.Name || orderSource.id}] ${msg}`, extra) : console.log(`[listing-import] [${orderSource?.Name || orderSource.id}] ${msg}`)
})

// Amazon's report open-date looks like "2023-05-02 10:21:33 CEST" — keep the
// date/time, drop the zone token (REST wants yyyy-MM-ddTHH:mm:ssZ).
export const parseRemoteDate = (v: string | null | undefined): string | null => {
  if (!v) return null
  const m = String(v).match(/^(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})/)
  if (m) return `${m[1]}T${m[2]}Z`
  // EU marketplaces: "30/01/2026 17:49:19 MET" (day/month/year); swap when the first part cannot be a day
  const eu = String(v).match(/^(\d{2})\/(\d{2})\/(\d{4})(?:[ T](\d{2}:\d{2}:\d{2}))?/)
  if (eu) {
    let [, a, b, y, t] = eu
    if (Number(b) > 12 && Number(a) <= 12) [a, b] = [b, a]
    return `${y}-${b}-${a}T${t || '00:00:00'}Z`
  }
  const m2 = String(v).match(/^(\d{4}-\d{2}-\d{2})/)
  if (m2) return `${m2[1]}T00:00:00Z`
  const d = new Date(String(v))
  return Number.isNaN(d.getTime()) ? null : restDate(d)
}

export const remoteStatusToListingStatus = (status: string | null | undefined): ListingStatus => {
  const s = String(status || '').toLowerCase()
  if (!s || s === 'active') return 'active'
  if (s === 'inactive' || s === 'incomplete') return 'inactive'
  return 'inactive'
}

/** Currency of the order source's price list, else by ISO code (marketplace default). */
export const resolveOrderSourceCurrencyId = async (f: IdempiereFetch, orderSource: any, isoFallback: string | null): Promise<{ id: number | null; iso: string | null }> => {
  const plId = Number(orderSource?.M_PriceList_ID?.id) || 0
  if (plId) {
    try {
      const pl: any = await f(`models/m_pricelist/${plId}`)
      const id = Number(pl?.C_Currency_ID?.id) || null
      if (id) return { id, iso: pl?.C_Currency_ID?.identifier || isoFallback }
    } catch { /* fall through */ }
  }
  if (isoFallback) {
    try {
      const res: any = await f(`models/c_currency?$filter=${enc(`ISO_Code eq '${esc(isoFallback)}'`)}&$top=1`)
      const id = Number(res?.records?.[0]?.id) || null
      if (id) return { id, iso: isoFallback }
    } catch { /* fall through */ }
  }
  return { id: null, iso: isoFallback }
}

export const buildImportPreview = async (event: any, token: string, f: IdempiereFetch, orderSource: any, opts: { forceRefresh?: boolean; maxWaitMs?: number } = {}): Promise<ImportPreview> => {
  const adapter = getListingAdapter(orderSource)
  if (!adapter || !supportsListings(orderSource)) throw Object.assign(new Error(`No listings adapter for marketplace "${marketplaceKeyOf(orderSource)}"`), { status: 400 })
  const ctx = makeCtx(f, orderSource)
  const remote = await adapter.importRemote(ctx, { forceRefresh: !!opts.forceRefresh, maxWaitMs: opts.maxWaitMs ?? 20000 })
  if (remote.pending) return { pending: true, startedAt: remote.startedAt }

  const config: any = useRuntimeConfig()
  const priceListId = config.public?.pricelistid || '101'
  const matched = await matchListingsToProducts(event, token, ctx.orgId, remote.rows.map((r: RemoteListing) => ({
    sellerSku: r.sellerSku, asin: r.externalProductId || '', itemName: r.title, price: r.price, quantity: r.qty,
    fulfillmentChannel: r.fulfillmentChannel, status: r.status, openDate: r.dateListed
  })), priceListId)
  const existing = await listListings(f, ctx.orderSourceId, { activeOnly: false, top: 5000 })
  const byKey = new Map<string, ListingRow>(existing.map(l => [l.listingKey, l]))

  // matched[] is index-aligned with remote.rows (SKUs may repeat or be empty on Shopify → never key by SKU)
  const rows: ImportPreviewRow[] = matched.map((m, i) => {
    const r = remote.rows[i]
    const ex = byKey.get(r?.listingKey || m.sellerSku) || null
    return {
      ...m,
      listingKey: r?.listingKey || m.sellerSku,
      externalProductId: r?.externalProductId || null, externalInventoryId: r?.externalInventoryId || null,
      imageUrl: r?.meta?.imageUrl || null, meta: r?.meta || null,
      currency: r?.currency || null,
      existingListingId: ex?.id || null, existingSyncStatus: ex?.syncStatus || null, existingPrice: ex?.price ?? null, existingQty: ex?.qty ?? null
    }
  })
  return {
    pending: false, rows, generatedAt: remote.generatedAt, fromCache: !!remote.fromCache,
    marketplace: ctx.marketplace, marketplaceDomain: amazonDomainForOrderSource(orderSource),
    counts: {
      total: rows.length, matched: rows.filter(r => r.productId).length, unmatched: rows.filter(r => !r.productId).length,
      alreadyImported: rows.filter(r => r.existingListingId).length, fba: rows.filter(r => String(r.fulfillmentChannel || 'DEFAULT').toUpperCase() !== 'DEFAULT').length
    }
  }
}

export const importRows = async (f: IdempiereFetch, orderSource: any, rows: ImportRowInput[], opts: ImportOptions = {}): Promise<ImportResult> => {
  const out: ImportResult = { created: 0, updated: 0, skipped: 0, productsCreated: 0, images: { imported: 0, hasImage: 0, noAmazonImage: 0, failed: 0 }, warnings: [], errors: [] }
  const orderSourceId = Number(orderSource.id)
  const orgId = Number(orderSource?.AD_Org_ID?.id) || 0
  if (!orderSourceId || !orgId) throw Object.assign(new Error('Order source has no organization'), { status: 400 })
  const existing = await listListings(f, orderSourceId, { activeOnly: false, top: 5000 })
  const byKey = new Map<string, ListingRow>(existing.map(l => [l.listingKey, l]))
  const currencyCache = new Map<string, { id: number | null; iso: string | null }>()
  const currencyFor = async (iso: string | null) => {
    const k = iso || ''
    if (!currencyCache.has(k)) currencyCache.set(k, await resolveOrderSourceCurrencyId(f, orderSource, iso))
    return currencyCache.get(k)!
  }
  const config: any = useRuntimeConfig()
  const priceListId = opts.priceListId || config.public?.pricelistid || '101'

  // Pictures: Amazon → catalog image of the ASIN (needs the SP-API connection); other marketplaces → the
  // picture URL the import preview carried (Shopify CDN). One connection failure disables the step.
  const marketplace = marketplaceKeyOf(orderSource)
  let amazon: { accessToken: string; marketplaceId: string; sellerId: string } | null = null
  let imagesEnabled = opts.importImages === true && supportsListings(orderSource)
  const amazonConnection = async () => {
    if (amazon || !imagesEnabled) return amazon
    try { amazon = await amazonConnectionFromOrderSource(orderSource) } catch (e: any) {
      imagesEnabled = false
      out.warnings.push('Image import disabled: ' + String(e?.message || e).slice(0, 200))
    }
    return amazon
  }

  // Product picture (only when the product has none) — best-effort, counted, never fails the row.
  const importImage = async (productId: number, asin: string | null, imageUrl: string | null, known?: { value: string; name: string; strapi: StrapiProductRef }) => {
    const product = known ? { id: productId, value: known.value, name: known.name, asin } : await loadProductForImage(f, productId)
    if (!product) return
    let res: { outcome: string; error?: string }
    if (marketplace === 'amazon') {
      const conn = await amazonConnection()
      if (!conn) return
      res = await importAmazonImageIfMissing(f, conn, product, asin, known?.strapi)
    } else {
      res = await importImageFromUrlIfMissing(f, product, imageUrl, known?.strapi)
    }
    if (res.outcome === 'imported') out.images.imported++
    else if (res.outcome === 'has-image') out.images.hasImage++
    else if (res.outcome === 'no-amazon-image') out.images.noAmazonImage++
    else { out.images.failed++; out.warnings.push(`${product.value || productId}: image not imported (${res.error || res.outcome})`) }
  }

  for (const r of rows) {
    const key = String(r.listingKey || r.sellerSku || '').trim()
    if (!key) { out.skipped++; continue }
    try {
      const ex = byKey.get(key)
      let createdProduct: { value: string; name: string; strapi: StrapiProductRef } | undefined
      // (never for rows that will be skipped anyway: an existing listing without overwrite)
      if (opts.createProducts && !r.productId && !ex?.productId && (!ex || opts.overwriteExisting)) {
        const cp = await createProductForListing(f, {
          orgId, name: r.amazonItemName || r.sellerSku || key, sellerSku: r.sellerSku || key, asin: r.asin || null,
          price: r.amazonPrice ?? null, priceListId, salesRepId: opts.salesRepId || null
        })
        r.productId = cp.productId
        r.productName = cp.name
        createdProduct = cp
        out.productsCreated++
        for (const w of cp.warnings) out.warnings.push(`${key}: ${w}`)
      }
      const remotePatch: Partial<ListingRow> = {
        remotePrice: r.amazonPrice ?? null, remoteQty: r.amazonQty ?? null, remoteStatus: r.status || null,
        remoteTitle: r.amazonItemName || null, fulfillmentChannel: r.fulfillmentChannel || 'DEFAULT',
        externalProductId: r.externalProductId || r.asin || null, externalInventoryId: r.externalInventoryId || null,
        ...(r.meta ? { meta: r.meta } : {}), lastSyncedAt: restDate(new Date())
      }
      if (ex) {
        if (!opts.overwriteExisting) { out.skipped++; continue }
        const patch: Partial<ListingRow> = { ...remotePatch }
        if (ex.price == null && ex.syncStatus === 'synced') patch.price = r.amazonPrice ?? null
        if (ex.qty == null && ex.syncStatus === 'synced') patch.qty = r.amazonQty ?? null
        if (!ex.productId && r.productId) { patch.productId = r.productId; if (r.productName) patch.name = r.productName }
        if (!ex.dateListed && r.openDate) patch.dateListed = parseRemoteDate(r.openDate)
        await updateListing(f, ex.id, patch)
        out.updated++
        const pid = patch.productId || ex.productId
        if (imagesEnabled && pid) await importImage(pid, r.asin || null, r.imageUrl || null, createdProduct)
        continue
      }
      const cur = await currencyFor(r.currency || null)
      const created = await insertListing(f, {
        ...remotePatch,
        orgId, orderSourceId, sellerSku: r.sellerSku || key, listingKey: key,
        name: (r.productName || r.amazonItemName || key).slice(0, 500),
        productId: r.productId || null,
        listingStatus: remoteStatusToListingStatus(r.status),
        price: r.amazonPrice ?? null, qty: r.amazonQty ?? null,
        currencyId: cur.id, isPriceTaxIncluded: true,
        dateListed: parseRemoteDate(r.openDate), syncStatus: 'synced', syncError: '',
        meta: { ...(r.meta || {}), importedFrom: marketplace === 'amazon' ? 'report' : marketplace, importedAt: restDate(new Date()) }
      })
      byKey.set(key, created)
      out.created++
      if (imagesEnabled && r.productId) await importImage(r.productId, r.asin || null, r.imageUrl || null, createdProduct)
    } catch (e: any) {
      out.errors.push({ listingKey: key, error: String(e?.data?.detail || e?.message || e).slice(0, 300) })
    }
  }
  return out
}
