import { eq } from 'drizzle-orm'
import type { Transaction } from '@platform-modules/db'
import {
  OrderIdempotencyConflictError,
  OrderIntegrityError,
  OrderValidationError,
} from './errors.js'
import { assertOrderIntegrity } from './integrity.js'
import { assembleOrder, loadOrderChildren } from './order-assembly.js'
import { order, orderLine, vendorSplit, type OrdersSchema } from './schema.js'
import {
  assertPositiveInt,
  assertUuid,
  type BuyerRef,
  type NewOrder,
  type Order,
  type OrderLine,
  type VendorSplit,
} from './types.js'

function parseBuyerRef(ref: BuyerRef): { buyerUserId: string | null; buyerGuestEmail: string | null } {
  const hasUser = 'userId' in ref && ref.userId !== undefined && ref.userId !== ''
  const hasGuest = 'guestEmail' in ref && ref.guestEmail !== undefined && ref.guestEmail !== ''

  if (hasUser && hasGuest) {
    throw new OrderValidationError('buyerRef')
  }
  if (!hasUser && !hasGuest) {
    throw new OrderValidationError('buyerRef')
  }

  if (hasUser) {
    assertUuid(ref.userId, 'userId')
    return { buyerUserId: ref.userId, buyerGuestEmail: null }
  }

  return { buyerUserId: null, buyerGuestEmail: (ref as { guestEmail: string }).guestEmail }
}

function assertSingleCurrency(orderCurrency: string, lines: NewOrder['lines']): void {
  for (const line of lines) {
    if (line.currency !== orderCurrency) {
      throw new OrderValidationError('currency')
    }
  }
}

function assertLineTotals(lines: NewOrder['lines']): void {
  for (const line of lines) {
    assertUuid(line.variantId, 'variantId')
    assertPositiveInt(line.qty, 'qty')
    const expected = line.unitPrice * BigInt(line.qty)
    if (line.lineTotal !== expected) {
      throw new OrderIntegrityError('lines')
    }
  }
}

function assertIdempotencyKey(idempotencyKey: string): void {
  if (idempotencyKey.length === 0) {
    throw new OrderValidationError('idempotencyKey')
  }
}

function bigintToDecimal(value: bigint): string {
  return value.toString()
}

function computeRequestHash(input: Omit<NewOrder, 'idempotencyKey'>): string {
  const payload = {
    buyerRef: input.buyerRef,
    currency: input.currency,
    priceMode: input.priceMode,
    subtotal: bigintToDecimal(input.subtotal),
    tax: bigintToDecimal(input.tax),
    discount: bigintToDecimal(input.discount),
    total: bigintToDecimal(input.total),
    lines: input.lines.map((line) => ({
      variantId: line.variantId,
      kind: line.kind,
      qty: line.qty,
      unitPrice: bigintToDecimal(line.unitPrice),
      lineTotal: bigintToDecimal(line.lineTotal),
      currency: line.currency,
      vendorId: line.vendorId ?? null,
    })),
    splits: input.splits.map((split) => ({
      vendorId: split.vendorId,
      amount: bigintToDecimal(split.amount),
      funder: split.funder,
    })),
  }
  return JSON.stringify(payload)
}

export async function createOrder(tx: Transaction<OrdersSchema>, input: NewOrder): Promise<Order> {
  assertIdempotencyKey(input.idempotencyKey)
  const buyer = parseBuyerRef(input.buyerRef)
  assertSingleCurrency(input.currency, input.lines)
  assertLineTotals(input.lines)
  assertOrderIntegrity(input)

  const requestHash = computeRequestHash(input)
  const now = new Date()

  const [created] = await tx
    .insert(order)
    .values({
      buyerUserId: buyer.buyerUserId,
      buyerGuestEmail: buyer.buyerGuestEmail,
      status: 'pending',
      currency: input.currency,
      priceMode: input.priceMode,
      subtotal: input.subtotal,
      tax: input.tax,
      discount: input.discount,
      total: input.total,
      idempotencyKey: input.idempotencyKey,
      requestHash,
      createdAt: now,
      updatedAt: now,
    })
    .onConflictDoNothing({ target: order.idempotencyKey })
    .returning()

  if (created) {
    const orderId = created.id

    const insertedLines =
      input.lines.length > 0
        ? await tx
            .insert(orderLine)
            .values(
              input.lines.map((line) => ({
                orderId,
                variantId: line.variantId,
                kind: line.kind,
                qty: line.qty,
                unitPrice: line.unitPrice,
                lineTotal: line.lineTotal,
                vendorId: line.vendorId ?? null,
                createdAt: now,
              })),
            )
            .returning()
        : []

    const insertedSplits =
      input.splits.length > 0
        ? await tx
            .insert(vendorSplit)
            .values(
              input.splits.map((split) => ({
                orderId,
                vendorId: split.vendorId,
                amount: split.amount,
                funder: split.funder,
                createdAt: now,
              })),
            )
            .returning()
        : []

    const lines: OrderLine[] = insertedLines.map((row) => ({
      id: row.id,
      orderId: row.orderId,
      variantId: row.variantId,
      kind: row.kind,
      qty: row.qty,
      unitPrice: row.unitPrice,
      lineTotal: row.lineTotal,
      vendorId: row.vendorId ?? null,
    }))

    const splits: VendorSplit[] = insertedSplits.map((row) => ({
      id: row.id,
      orderId: row.orderId,
      vendorId: row.vendorId ?? null,
      amount: row.amount,
      funder: row.funder,
    }))

    return assembleOrder(created, lines, splits, {})
  }

  const [existing] = await tx
    .select()
    .from(order)
    .where(eq(order.idempotencyKey, input.idempotencyKey))

  if (!existing) {
    throw new OrderValidationError('idempotencyKey')
  }

  if (existing.requestHash !== requestHash) {
    throw new OrderIdempotencyConflictError({ idempotencyKey: input.idempotencyKey })
  }

  const { lines, splits, steps } = await loadOrderChildren(tx, existing.id)
  return assembleOrder(existing, lines, splits, steps)
}
