import { MixedCurrencyError } from './errors.js'
import type { CartStore } from './store.js'
import type { Cart, CartLine } from './types.js'

function cloneCart(cart: Cart): Cart {
  return {
    id: cart.id,
    currency: cart.currency,
    lines: cart.lines.map((line) => ({ ...line, price: { ...line.price } })),
    subtotal: cart.subtotal,
  }
}

function computeSubtotal(lines: CartLine[]): bigint {
  return lines.reduce((sum, line) => sum + line.price.amount * BigInt(line.qty), 0n)
}

function assertMergeCurrency(guest: Cart, user: Cart): void {
  const guestCurrency = guest.lines.length > 0 ? guest.currency : ''
  const userCurrency = user.lines.length > 0 ? user.currency : ''

  if (guestCurrency !== '' && userCurrency !== '' && guestCurrency !== userCurrency) {
    throw new MixedCurrencyError(userCurrency, guestCurrency)
  }
}

function mergeLines(userLines: CartLine[], guestLines: CartLine[]): CartLine[] {
  const merged = userLines.map((line) => ({ ...line, price: { ...line.price } }))
  const indexByVariant = new Map(merged.map((line, index) => [line.variantId, index]))

  for (const guestLine of guestLines) {
    const existingIndex = indexByVariant.get(guestLine.variantId)
    if (existingIndex === undefined) {
      indexByVariant.set(guestLine.variantId, merged.length)
      merged.push({ ...guestLine, price: { ...guestLine.price } })
      continue
    }

    const existing = merged[existingIndex]!
    merged[existingIndex] = {
      ...existing,
      qty: existing.qty + guestLine.qty,
      price: { ...existing.price },
    }
  }

  return merged
}

export function createMemoryCartStore(): CartStore {
  const carts = new Map<string, Cart>()

  return {
    async load(cartId) {
      const cart = carts.get(cartId)
      return cart ? cloneCart(cart) : null
    },

    async save(cart) {
      carts.set(cart.id, cloneCart(cart))
    },

    async merge(guestCartId, userId) {
      const guest = (await this.load(guestCartId)) ?? {
        id: guestCartId,
        currency: '',
        lines: [],
        subtotal: 0n,
      }
      // Test-double simplification: userId doubles as the in-memory cart key (not a real WHERE userId=? lookup).
      const user = (await this.load(userId)) ?? {
        id: userId,
        currency: '',
        lines: [],
        subtotal: 0n,
      }

      assertMergeCurrency(guest, user)

      const currency =
        user.lines.length > 0
          ? user.currency
          : guest.lines.length > 0
            ? guest.currency
            : ''

      const lines = mergeLines(user.lines, guest.lines)
      const merged: Cart = {
        id: userId,
        currency,
        lines,
        subtotal: computeSubtotal(lines),
      }

      await this.save(merged)
      carts.delete(guestCartId)
      return cloneCart(merged)
    },
  }
}
