import { commitStock, setInventory } from '@platform-modules/commerce-inventory'
import type { InventorySchema } from '@platform-modules/commerce-inventory'
import type { Transaction } from '@platform-modules/db'
import {
  getDefaultInventoryLocationId,
  listTrackedInvoiceStockItems,
  postInventoryReceipt,
  reverseInventorySale,
  type DbTx,
} from '@zync/db/queries'

export {
  commitStock,
  consume,
  getAvailability,
  release,
  reserve,
  setInventory,
} from '@platform-modules/commerce-inventory'

type InventoryTx = Transaction<InventorySchema>

interface InventoryPostReceiptInput {
  tenantId: string
  itemId: string
  locationId: string
  qty: number
  unitCost: number
  holderRef?: string
  occurredAt: Date
}

export const inventoryPosting = {
  postReceipt: postStockReceipt,
}

interface ExpenseStockLineRecord {
  id?: unknown
  holderRef?: unknown
  itemId?: unknown
  stockItemId?: unknown
  locationId?: unknown
  qty?: unknown
  quantity?: unknown
  receivedQty?: unknown
  unitCost?: unknown
  unitNetCost?: unknown
  netUnitCost?: unknown
  unitCostNet?: unknown
  grossUnitCost?: unknown
  unitGrossCost?: unknown
  unitVatAmount?: unknown
  vatPerUnit?: unknown
  netAmount?: unknown
  totalNetAmount?: unknown
  amount?: unknown
  totalAmount?: unknown
  grossAmount?: unknown
  vatAmount?: unknown
  totalVatAmount?: unknown
  taxAmount?: unknown
  receivedAt?: unknown
}

interface ExpenseReceiptLike {
  id: string
  tenantId: string
  amount?: string | null
  vatAmount?: string | null
  processedAt?: string | null
  updatedAt?: string | null
  createdAt?: string | null
  sourceMetadata?: Record<string, unknown> | null
}

interface NormalizedStockReceiptLine {
  holderRef: string
  itemId: string
  locationId: string
  qty: number
  unitCost: number
  occurredAt: Date
}

function toFiniteNumber(value: unknown): number | null {
  if (typeof value === 'number') {
    return Number.isFinite(value) ? value : null
  }
  if (typeof value === 'string' && value.trim() !== '') {
    const parsed = Number(value)
    return Number.isFinite(parsed) ? parsed : null
  }
  return null
}

function toNonEmptyString(value: unknown): string | null {
  return typeof value === 'string' && value.trim() !== '' ? value : null
}

async function postStockReceipt(
  tx: DbTx,
  input: InventoryPostReceiptInput,
): Promise<void> {
  await postInventoryReceipt(tx, input)
}

function getStockLineRecords(sourceMetadata: Record<string, unknown> | null | undefined): ExpenseStockLineRecord[] {
  const candidates = [
    sourceMetadata?.stockLines,
    sourceMetadata?.stock_lines,
    sourceMetadata?.inventory && typeof sourceMetadata.inventory === 'object'
      ? (sourceMetadata.inventory as Record<string, unknown>).stockLines
      : null,
    sourceMetadata?.receiveIntoStock && typeof sourceMetadata.receiveIntoStock === 'object'
      ? (sourceMetadata.receiveIntoStock as Record<string, unknown>).lines
      : null,
  ]

  for (const candidate of candidates) {
    if (Array.isArray(candidate)) {
      return candidate as ExpenseStockLineRecord[]
    }
  }

  return []
}

function resolveLineQty(line: ExpenseStockLineRecord): number | null {
  const qty = toFiniteNumber(line.qty ?? line.quantity ?? line.receivedQty)
  return qty && qty > 0 ? qty : null
}

function resolveLineNetUnitCost(
  line: ExpenseStockLineRecord,
  qty: number,
  fallbackNetAmount: number | null,
): number | null {
  const direct = toFiniteNumber(
    line.unitNetCost ?? line.netUnitCost ?? line.unitCostNet,
  )
  if (direct !== null) return direct

  const grossUnit = toFiniteNumber(line.grossUnitCost ?? line.unitGrossCost)
  if (grossUnit !== null) {
    const unitVat = toFiniteNumber(line.unitVatAmount ?? line.vatPerUnit) ?? 0
    return grossUnit - unitVat
  }

  const netAmount = toFiniteNumber(line.netAmount ?? line.totalNetAmount)
  if (netAmount !== null) {
    return netAmount / qty
  }

  const grossAmount = toFiniteNumber(line.amount ?? line.totalAmount ?? line.grossAmount)
  if (grossAmount !== null) {
    const vatAmount = toFiniteNumber(line.vatAmount ?? line.totalVatAmount ?? line.taxAmount) ?? 0
    return (grossAmount - vatAmount) / qty
  }

  const unitCost = toFiniteNumber(line.unitCost)
  if (unitCost !== null) return unitCost

  if (fallbackNetAmount !== null) {
    return fallbackNetAmount / qty
  }

  return null
}

function normalizeStockReceiptLines(expense: ExpenseReceiptLike): NormalizedStockReceiptLine[] {
  const lines = getStockLineRecords(expense.sourceMetadata)
  if (lines.length === 0) {
    return []
  }

  const expenseNetAmount = (() => {
    const amount = toFiniteNumber(expense.amount)
    if (amount === null) return null
    const vatAmount = toFiniteNumber(expense.vatAmount) ?? 0
    return amount - vatAmount
  })()

  return lines.map((line, index) => {
    const itemId = toNonEmptyString(line.itemId ?? line.stockItemId)
    if (!itemId) {
      throw new Error(`Expense ${expense.id} stock line ${index + 1} is missing itemId/stockItemId`)
    }

    const locationId = toNonEmptyString(line.locationId)
    if (!locationId) {
      throw new Error(`Expense ${expense.id} stock line ${index + 1} is missing locationId`)
    }

    const qty = resolveLineQty(line)
    if (qty === null) {
      throw new Error(`Expense ${expense.id} stock line ${index + 1} must have a positive qty`)
    }

    const fallbackNetAmount = lines.length === 1 ? expenseNetAmount : null
    const unitCost = resolveLineNetUnitCost(line, qty, fallbackNetAmount)
    if (unitCost === null) {
      throw new Error(`Expense ${expense.id} stock line ${index + 1} is missing net cost`)
    }

    return {
      holderRef:
        toNonEmptyString(line.holderRef)
        ?? `expense:${expense.id}:stock-line:${toNonEmptyString(line.id) ?? String(index + 1)}`,
      itemId,
      locationId,
      qty,
      unitCost,
      occurredAt: new Date(
        toNonEmptyString(line.receivedAt)
        ?? expense.processedAt
        ?? expense.updatedAt
        ?? expense.createdAt
        ?? new Date().toISOString(),
      ),
    }
  })
}

export async function provisionStockItem(
  tx: DbTx,
  input: { tenantId: string; stockItemId: string },
): Promise<string> {
  await setInventory(tx as unknown as InventoryTx, {
    skuId: input.stockItemId,
    vendorId: input.tenantId,
    quantityTotal: null,
  })
  return input.stockItemId
}

async function getDefaultLocationId(
  tx: DbTx,
  tenantId: string,
): Promise<string> {
  return getDefaultInventoryLocationId(tx, tenantId)
}

export async function postProductOpeningBalance(
  tx: DbTx,
  input: { tenantId: string; itemId: string; quantity: number; unitCost: number; holderRef: string },
): Promise<void> {
  if (!(input.quantity > 0)) {
    return
  }

  const locationId = await getDefaultLocationId(tx, input.tenantId)
  await inventoryPosting.postReceipt(tx, {
    tenantId: input.tenantId,
    itemId: input.itemId,
    locationId,
    qty: input.quantity,
    unitCost: input.unitCost,
    holderRef: input.holderRef,
    occurredAt: new Date(),
  })
}

export async function postIssue(
  tx: DbTx,
  input: { tenantId: string; invoiceId: string },
): Promise<void> {
  const items = await listTrackedInvoiceStockItems(tx, input.tenantId, input.invoiceId)
  for (const item of items) {
    await commitStock(tx as unknown as InventoryTx, item.stockItemId, item.quantity)
  }
}

export async function reverseMovement(
  tx: DbTx,
  input: { tenantId: string; invoiceId: string },
): Promise<void> {
  const items = await listTrackedInvoiceStockItems(tx, input.tenantId, input.invoiceId)
  for (const item of items) {
    await reverseInventorySale(tx, {
      tenantId: input.tenantId,
      itemId: item.stockItemId,
      quantity: item.quantity,
    })
  }
}

export async function postExpenseReceipts(
  tx: DbTx,
  expense: ExpenseReceiptLike,
): Promise<number> {
  const lines = normalizeStockReceiptLines(expense)

  for (const line of lines) {
    await inventoryPosting.postReceipt(tx, {
      tenantId: expense.tenantId,
      itemId: line.itemId,
      locationId: line.locationId,
      qty: line.qty,
      unitCost: line.unitCost,
      holderRef: line.holderRef,
      occurredAt: line.occurredAt,
    })
  }

  return lines.length
}
