import type { AttributeRow, CategoryRow, CategoryStoreRow, ProductRecord, ProductStoreRow, StoreRow } from '@znet/catalog-db'
import { attrString, type Category, type Product, type ProductAttribute, type StoreConfig, type StoreView, type Website } from '@znet/magento'

/** Magento sends `YYYY-MM-DD HH:MM:SS` in UTC. */
export function parseMagentoDate(v: string | null | undefined): Date | null {
  if (!v) return null
  const d = new Date(v.includes('T') ? v : `${v.replace(' ', 'T')}Z`)
  return Number.isNaN(d.getTime()) ? null : d
}

export function toMagentoDate(d: Date): string {
  return d.toISOString().slice(0, 19).replace('T', ' ')
}

const num = (v: string | number | null | undefined): number | null => {
  if (v == null || v === '') return null
  const n = Number(v)
  return Number.isFinite(n) ? n : null
}

const dateOnly = (v: string | null): string | null => (v ? v.slice(0, 10) : null)

export function mapStores(views: StoreView[], configs: StoreConfig[], websites: Website[]): StoreRow[] {
  const cfg = new Map(configs.map(c => [c.id, c]))
  const web = new Map(websites.map(w => [w.id, w]))
  return views.map(v => {
    const c = cfg.get(v.id)
    return {
      storeId: v.id,
      code: v.code,
      name: v.name,
      websiteId: v.website_id,
      websiteCode: web.get(v.website_id)?.code ?? null,
      groupId: v.store_group_id,
      locale: c?.locale ?? null,
      baseCurrency: c?.base_currency_code ?? null,
      displayCurrency: c?.default_display_currency_code ?? null,
      timezone: c?.timezone ?? null,
      baseUrl: c?.base_url ?? null,
      mediaUrl: c?.base_media_url ?? null,
      isActive: v.id === 0 || v.is_active === 1,
      syncedAt: new Date(),
    }
  })
}

export function mapAttribute(a: ProductAttribute): AttributeRow {
  return {
    attributeCode: a.attribute_code,
    attributeId: a.attribute_id,
    label: a.default_frontend_label ?? null,
    frontendInput: a.frontend_input ?? 'text',
    isRequired: Boolean(a.is_required),
    isUserDefined: Boolean(a.is_user_defined),
    scope: a.scope ?? null,
    // Magento includes a blank " " placeholder option for selects; drop it.
    options: (a.options ?? []).filter(o => String(o.value) !== '').map(o => ({ value: String(o.value), label: o.label })),
    syncedAt: new Date(),
  }
}

export function mapCategory(c: Category): CategoryRow {
  return { categoryId: c.id, parentId: c.parent_id, path: c.path, level: c.level, position: c.position, syncedAt: new Date() }
}

export function mapCategoryStore(c: Category, storeId: number): CategoryStoreRow {
  return {
    categoryId: c.id,
    storeId,
    name: c.name,
    urlKey: attrString(c, 'url_key'),
    isActive: Boolean(c.is_active),
    includeInMenu: c.include_in_menu === undefined ? true : Boolean(c.include_in_menu),
  }
}

/** Store-scoped content of a product as returned by `/rest/{store}/V1/products`. */
export function mapProductStore(p: Product, storeId: number): ProductStoreRow {
  const attributes: Record<string, string | string[] | null> = {}
  for (const a of p.custom_attributes ?? []) attributes[a.attribute_code] = a.value
  return {
    productId: p.id,
    storeId,
    name: p.name ?? p.sku,
    status: p.status,
    visibility: p.visibility,
    urlKey: attrString(p, 'url_key'),
    description: attrString(p, 'description'),
    shortDescription: attrString(p, 'short_description'),
    metaTitle: attrString(p, 'meta_title'),
    metaDescription: attrString(p, 'meta_description'),
    attributes,
    syncedAt: new Date(),
  }
}

/**
 * Default-scope product → catalog record.
 * `childSkuById` resolves configurable child ids (Magento only returns ids for those).
 */
export function mapProduct(p: Product, childSkuById: Map<number, string>): ProductRecord {
  const ext = p.extension_attributes ?? {}

  const relations: ProductRecord['relations'] = []
  for (const id of ext.configurable_product_links ?? []) {
    const sku = childSkuById.get(id)
    if (sku) relations.push({ childSku: sku, kind: 'configurable' })
  }
  for (const opt of ext.bundle_product_options ?? []) {
    for (const l of opt.product_links ?? []) relations.push({ childSku: l.sku, kind: 'bundle' })
  }
  const links: ProductRecord['links'] = []
  for (const l of p.product_links ?? []) {
    if (l.link_type === 'associated') relations.push({ childSku: l.linked_product_sku, kind: 'grouped' })
    else links.push({ linkType: l.link_type, linkedSku: l.linked_product_sku, position: l.position ?? 0 })
  }

  return {
    product: {
      productId: p.id,
      sku: p.sku,
      typeId: p.type_id,
      attributeSetId: p.attribute_set_id,
      websiteIds: ext.website_ids ?? [],
      price: num(p.price),
      specialPrice: num(attrString(p, 'special_price')),
      specialFrom: dateOnly(attrString(p, 'special_from_date')),
      specialTo: dateOnly(attrString(p, 'special_to_date')),
      tierPrices: (p.tier_prices ?? []).map(t => ({
        customerGroupId: t.customer_group_id,
        websiteId: t.extension_attributes?.website_id ?? null,
        qty: t.qty,
        value: t.value,
        percentage: t.extension_attributes?.percentage_value ?? null,
      })),
      weight: num(p.weight),
      magentoCreatedAt: parseMagentoDate(p.created_at),
      magentoUpdatedAt: parseMagentoDate(p.updated_at) ?? new Date(),
      syncedAt: new Date(),
    },
    storeData: mapProductStore(p, 0),
    relations: dedupe(relations, r => `${r.kind}:${r.childSku}`),
    links: dedupe(links, l => `${l.linkType}:${l.linkedSku}`),
    categories: dedupe(
      (ext.category_links ?? []).map(c => ({ categoryId: Number(c.category_id), position: c.position ?? 0 })),
      c => String(c.categoryId),
    ),
    images: dedupe(
      (p.media_gallery_entries ?? []).map(m => ({
        file: m.file,
        label: m.label || null,
        position: m.position ?? 0,
        roles: m.types ?? [],
        disabled: Boolean(m.disabled),
        mediaType: m.media_type ?? 'image',
      })),
      i => i.file,
    ),
  }
}

function dedupe<T>(rows: T[], key: (r: T) => string): T[] {
  const seen = new Map<string, T>()
  for (const r of rows) if (!seen.has(key(r))) seen.set(key(r), r)
  return [...seen.values()]
}
