import { eq } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { ImmutableFieldError, ProductValidationError, type ImmutableField } from './errors.js'
import { getProductById, SLUG_RE } from './reads.js'
import { product, variantPrice, type CatalogSchema } from './schema.js'
import type { Product, ProductInput, ProductStatus, VariantPrice } from './types.js'

/**
 * Throw `ImmutableFieldError(field)` ONLY when the caller explicitly supplies a DIFFERENT
 * value for a create-time-frozen field (`provided !== undefined && provided !== existing`).
 * Same-value or omitted = idempotent no-op (a full-object resubmit must never throw).
 */
function assertImmutableUnchanged(field: ImmutableField, provided: unknown, existing: unknown): void {
  if (provided !== undefined && provided !== existing) {
    throw new ImmutableFieldError(field)
  }
}

function isUniqueViolation(e: unknown): boolean {
  let cur: unknown = e
  while (cur) {
    const code = (cur as { code?: unknown })?.code
    const msg = cur instanceof Error ? cur.message : String(cur)
    if (code === '23505' || /product_vendor_slug_uq|duplicate key|unique constraint/i.test(msg)) return true
    cur = cur instanceof Error ? (cur as Error & { cause?: unknown }).cause : undefined
  }
  return false
}

async function catchSlugDuplicate<T>(fn: () => Promise<T>): Promise<T> {
  try {
    return await fn()
  } catch (e) {
    if (isUniqueViolation(e)) throw new ProductValidationError('slug', 'duplicate')
    throw e
  }
}

export async function upsertProduct(db: Querier<CatalogSchema>, input: ProductInput): Promise<Product> {
  if (!SLUG_RE.test(input.slug)) {
    throw new ProductValidationError('slug')
  }

  const vendorId = input.vendorId ?? null
  const now = new Date()

  if (input.id) {
    const [existing] = await db.select().from(product).where(eq(product.id, input.id)).limit(1)
    if (existing) {
      // Create-time-frozen fields: throw ONLY on an explicit DIFFERENT value; same-value/omitted
      // is an idempotent no-op (a full-object resubmit must succeed). NEVER re-written in .set().
      assertImmutableUnchanged('kind', input.kind, existing.kind)
      assertImmutableUnchanged('vendorId', input.vendorId, existing.vendorId)

      await catchSlugDuplicate(() =>
        db
          .update(product)
          .set({
            slug: input.slug,
            title: input.title,
            description: input.description ?? existing.description,
            status: input.status ?? existing.status,
            media: input.media ?? existing.media,
            tags: input.tags ?? existing.tags,
            availableFrom: input.availableFrom !== undefined ? input.availableFrom : existing.availableFrom,
            availableUntil: input.availableUntil !== undefined ? input.availableUntil : existing.availableUntil,
            updatedAt: now,
          })
          .where(eq(product.id, input.id!)),
      )

      const updated = await getProductById(db, input.id, { audience: 'admin' })
      if (!updated) throw new ProductValidationError('id', 'not found after update')
      return updated
    }
  }

  const [created] = await catchSlugDuplicate(() =>
    db
      .insert(product)
      .values({
        id: input.id,
        kind: input.kind,
        vendorId,
        slug: input.slug,
        title: input.title,
        description: input.description,
        status: input.status ?? 'draft',
        media: input.media ?? [],
        tags: input.tags ?? [],
        availableFrom: input.availableFrom ?? null,
        availableUntil: input.availableUntil ?? null,
        createdAt: now,
        updatedAt: now,
      })
      .returning(),
  )

  const hydrated = await getProductById(db, created!.id, { audience: 'admin' })
  if (!hydrated) throw new ProductValidationError('id', 'not found after create')
  return hydrated
}

export async function setVariantPrices(
  db: Querier<CatalogSchema>,
  variantId: string,
  prices: VariantPrice[],
): Promise<void> {
  await db.delete(variantPrice).where(eq(variantPrice.variantId, variantId))
  if (prices.length === 0) return
  await db.insert(variantPrice).values(
    prices.map((p) => ({
      variantId,
      currency: p.currency,
      amount: p.amount,
      priceMode: p.priceMode,
    })),
  )
}

export async function setProductStatus(
  db: Querier<CatalogSchema>,
  id: string,
  status: ProductStatus,
): Promise<void> {
  await db.update(product).set({ status, updatedAt: new Date() }).where(eq(product.id, id))
}
