import { Hono } from 'hono'
import {
  ProductNameConflictError,
  createProduct,
  deleteProduct,
  getProduct,
  getProductInventoryHistory,
  listProducts,
  productInUse,
  setProductActive,
  setProductStockItemId,
  updateProduct,
} from '@zync/db/queries'
import type { AppEnv } from '../types'
import { authMiddleware } from '../middleware/auth'
import { requirePermission } from '../middleware/guards'
import { requireModuleEnabled } from '../middleware/require-module-enabled'
import {
  createProductSchema,
  listProductsQuerySchema,
  updateProductSchema,
} from '@zync/db/validation/products'
import { postProductOpeningBalance, provisionStockItem } from '../integrations/platform/inventory'

export const productsRoute = new Hono<AppEnv>()

function getOpeningBalanceInput(
  input: { openingBalanceQuantity?: number; openingBalanceUnitCost?: number },
): { quantity: number; unitCost: number } | null {
  if (!(input.openingBalanceQuantity && input.openingBalanceQuantity > 0)) {
    return null
  }

  return {
    quantity: input.openingBalanceQuantity,
    unitCost: input.openingBalanceUnitCost ?? 0,
  }
}

async function ensureTrackedProductStock(
  db: AppEnv['Variables']['db'],
  tenantId: string,
  product: Awaited<ReturnType<typeof createProduct>>,
) {
  if (!product.isTracked || product.stockItemId) {
    return product
  }

  return db.transaction(async (tx) => {
    const stockItemId = await provisionStockItem(tx, {
      tenantId,
      stockItemId: product.id,
    })
    const updated = await setProductStockItemId(tx, tenantId, product.id, stockItemId)
    if (!updated) {
      throw new Error(`Tracked product ${product.id} disappeared before stock provisioning completed`)
    }
    return updated
  })
}

productsRoute.use('*', authMiddleware)
productsRoute.use('*', requireModuleEnabled('invoices'))

productsRoute.get('/', requirePermission('invoices:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)

  const parsed = listProductsQuerySchema.safeParse({
    q: c.req.query('q'),
    category: c.req.query('category'),
    limit: c.req.query('limit'),
    includeArchived: c.req.query('includeArchived'),
  })
  if (!parsed.success) return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)

  const items = await listProducts(c.get('db'), session.tid, parsed.data)
  return c.json({ items })
})

productsRoute.get('/:id', requirePermission('invoices:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)

  const product = await getProduct(c.get('db'), session.tid, c.req.param('id'))
  if (!product) return c.json({ error: 'Not found' }, 404)
  return c.json(product)
})

productsRoute.get('/:id/inventory-history', requirePermission('inventory:read'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)

  const history = await getProductInventoryHistory(c.get('db'), session.tid, c.req.param('id'))
  if (!history) return c.json({ error: 'Not found' }, 404)
  return c.json(history)
})

productsRoute.post('/', requirePermission('settings:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid

  const parsed = createProductSchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)

  try {
    const db = c.get('db')
    const product = await createProduct(db, tenantId, parsed.data)
    const provisioned = await ensureTrackedProductStock(db, tenantId, product)
    const openingBalance = getOpeningBalanceInput(parsed.data)
    if (provisioned.isTracked && provisioned.stockItemId && openingBalance) {
      await db.transaction(async (tx) => {
        await postProductOpeningBalance(tx, {
          tenantId,
          itemId: provisioned.stockItemId!,
          quantity: openingBalance.quantity,
          unitCost: openingBalance.unitCost,
          holderRef: `product:${provisioned.id}:opening-balance`,
        })
      })
    }
    return c.json(provisioned, 201)
  } catch (error) {
    if (error instanceof ProductNameConflictError) return c.json({ error: error.message }, 409)
    throw error
  }
})

productsRoute.patch('/:id', requirePermission('settings:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const tenantId = session.tid

  const parsed = updateProductSchema.safeParse(await c.req.json().catch(() => null))
  if (!parsed.success) return c.json({ error: 'Validation failed', issues: parsed.error.issues }, 400)

  try {
    const db = c.get('db')
    const existing = await getProduct(db, tenantId, c.req.param('id'))
    if (!existing) return c.json({ error: 'Not found' }, 404)
    const product = await updateProduct(db, tenantId, c.req.param('id'), parsed.data)
    if (!product) return c.json({ error: 'Not found' }, 404)
    const provisioned = await ensureTrackedProductStock(db, tenantId, product)
    const openingBalance = getOpeningBalanceInput(parsed.data)
    if (!existing.isTracked && provisioned.isTracked && provisioned.stockItemId && openingBalance) {
      await db.transaction(async (tx) => {
        await postProductOpeningBalance(tx, {
          tenantId,
          itemId: provisioned.stockItemId!,
          quantity: openingBalance.quantity,
          unitCost: openingBalance.unitCost,
          holderRef: `product:${provisioned.id}:opening-balance`,
        })
      })
    }
    return c.json(provisioned)
  } catch (error) {
    if (error instanceof ProductNameConflictError) return c.json({ error: error.message }, 409)
    throw error
  }
})

productsRoute.put('/:id/archive', requirePermission('settings:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const product = await setProductActive(c.get('db'), session.tid, c.req.param('id'), false)
  if (!product) return c.json({ error: 'Not found' }, 404)
  return c.json(product)
})

productsRoute.put('/:id/restore', requirePermission('settings:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)
  const product = await setProductActive(c.get('db'), session.tid, c.req.param('id'), true)
  if (!product) return c.json({ error: 'Not found' }, 404)
  return c.json(product)
})

productsRoute.delete('/:id', requirePermission('settings:write'), async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) return c.json({ error: 'Unauthorized' }, 401)

  const id = c.req.param('id')
  if (await productInUse(c.get('db'), session.tid, id)) {
    return c.json({ error: 'Product is used in recent invoices. Archive it instead.' }, 409)
  }

  await deleteProduct(c.get('db'), session.tid, id)
  return c.body(null, 204)
})
