import type { Querier, PostgresTransaction } from '@platform-modules/db'
import type { CatalogSchema } from '@platform-modules/commerce-catalog'
import { getProductById, upsertProduct } from '@platform-modules/commerce-catalog'
import type { Product, ProductInput } from '@platform-modules/commerce-catalog'
import type { Actor } from '@platform-modules/commerce-orders'
import { assertVendorCanSell, requireVendorForActor } from './authz.js'
import { VendorProductNotFoundError } from './errors.js'
import type { MarketplaceDbSchema } from './schema.js'
import type { ProductPatch } from './types.js'

export async function createVendorProduct(
  tx: PostgresTransaction<MarketplaceDbSchema>,
  actor: Actor,
  input: ProductInput,
): Promise<Product> {
  const v = await requireVendorForActor(tx, actor)
  await assertVendorCanSell(tx, v.id)
  // C4 + A5: strip any client-supplied `id` — create is always an INSERT with a
  // server-minted id. Forwarding a client id turns this into an upsert that can
  // target another vendor's row (blocked by catalog immutability, but as a 500-ish
  // ImmutableFieldError) and leaks an existence oracle (id-exists vs id-absent).
  const { id: _dropClientId, ...rest } = input as ProductInput & { id?: string }
  return upsertProduct(tx as unknown as Querier<CatalogSchema>, { ...rest, vendorId: v.id })
}

export async function updateVendorProduct(
  tx: PostgresTransaction<MarketplaceDbSchema>,
  actor: Actor,
  productId: string,
  patch: ProductPatch,
): Promise<Product> {
  const catalogTx = tx as unknown as Querier<CatalogSchema>
  const product = await getProductById(catalogTx, productId, { audience: 'admin' })
  if (!product) throw new VendorProductNotFoundError({ productId })

  if (product.vendorId == null) {
    throw new VendorProductNotFoundError({ productId })
  }

  if (!actor.isAdmin) {
    const v = await requireVendorForActor(tx, actor)
    if (v.id !== product.vendorId) {
      throw new VendorProductNotFoundError({ productId })
    }
    await assertVendorCanSell(tx, v.id)
  }

  // C4 + A5: kind/vendorId are immutable and NOT part of ProductPatch. Take them
  // unconditionally from the loaded row — never from the (possibly raw-HTTP-parsed)
  // patch. Reading them off `patch` would let a foreign value reach catalog and
  // surface a 500-ish ImmutableFieldError existence oracle instead of a clean no-op.
  const merged: ProductInput = {
    id: product.id,
    kind: product.kind,
    vendorId: product.vendorId,
    slug: patch.slug ?? product.slug,
    title: patch.title ?? product.title,
    description: patch.description !== undefined ? patch.description : product.description,
    status: patch.status ?? product.status,
    media: patch.media ?? product.media,
    tags: patch.tags ?? product.tags,
    availableFrom:
      patch.availableFrom !== undefined ? patch.availableFrom : product.availableFrom,
    availableUntil:
      patch.availableUntil !== undefined ? patch.availableUntil : product.availableUntil,
  }

  return upsertProduct(catalogTx, merged)
}
