import { and, asc, eq } from 'drizzle-orm'
import type { Db } from '../client'
import { tenantSettings } from '../schema/tenants'
import { stockLocations } from '../schema/stock-locations'

export interface InventorySettingsObject {
  inventory_method: string
  allow_negative: boolean
}

export interface UpdateInventorySettingsInput {
  inventory_method?: string
  allow_negative?: boolean
}

export interface InventoryLocationObject {
  id: string
  name: string
  code: string | null
  isDefault: boolean
  isActive: boolean
}

export interface CreateInventoryLocationInput {
  name: string
  code?: string | null
  isDefault?: boolean
  isActive?: boolean
}

export interface UpdateInventoryLocationInput {
  name?: string
  code?: string | null
  isDefault?: boolean
  isActive?: boolean
}

export class InventoryLocationConflictError extends Error {}

function normalizeLocationName(value: string): string {
  return value.trim()
}

function normalizeLocationCode(value: string | null | undefined): string | null {
  const normalized = value?.trim() ?? ''
  return normalized ? normalized : null
}

function serializeLocation(
  row: {
    id: string
    name: string
    code: string | null
    isDefault: boolean
    isActive: boolean
  } | null | undefined,
): InventoryLocationObject | null {
  if (!row) return null
  return {
    id: row.id,
    name: row.name,
    code: row.code,
    isDefault: row.isDefault,
    isActive: row.isActive,
  }
}

async function unsetDefaultLocation(db: Db, tenantId: string): Promise<void> {
  await db
    .update(stockLocations)
    .set({ isDefault: false, updatedAt: new Date() })
    .where(eq(stockLocations.tenantId, tenantId))
}

async function findInventoryLocation(
  db: Db,
  tenantId: string,
  locationId: string,
): Promise<InventoryLocationObject | null> {
  const rows = await db
    .select({
      id: stockLocations.id,
      name: stockLocations.name,
      code: stockLocations.code,
      isDefault: stockLocations.isDefault,
      isActive: stockLocations.isActive,
    })
    .from(stockLocations)
    .where(and(eq(stockLocations.tenantId, tenantId), eq(stockLocations.id, locationId)))
    .orderBy(asc(stockLocations.name))

  return serializeLocation(rows[0] ?? null)
}

export async function listInventoryLocations(
  db: Db,
  tenantId: string,
): Promise<InventoryLocationObject[]> {
  const rows = await db
    .select({
      id: stockLocations.id,
      name: stockLocations.name,
      code: stockLocations.code,
      isDefault: stockLocations.isDefault,
      isActive: stockLocations.isActive,
    })
    .from(stockLocations)
    .where(eq(stockLocations.tenantId, tenantId))
    .orderBy(asc(stockLocations.name))

  return rows.map((row) => serializeLocation(row)!).filter(Boolean)
}

export async function createInventoryLocation(
  db: Db,
  tenantId: string,
  input: CreateInventoryLocationInput,
): Promise<InventoryLocationObject> {
  if (input.isDefault) {
    await unsetDefaultLocation(db, tenantId)
  }

  const rows = await db
    .insert(stockLocations)
    .values({
      tenantId,
      name: normalizeLocationName(input.name),
      code: normalizeLocationCode(input.code),
      isDefault: input.isDefault ?? false,
      isActive: input.isActive ?? true,
    })
    .returning({
      id: stockLocations.id,
      name: stockLocations.name,
      code: stockLocations.code,
      isDefault: stockLocations.isDefault,
      isActive: stockLocations.isActive,
    })

  const created = serializeLocation(rows[0] ?? null)
  if (!created) throw new Error('Inventory location insert failed')
  return created
}

export async function updateInventoryLocation(
  db: Db,
  tenantId: string,
  locationId: string,
  patch: UpdateInventoryLocationInput,
): Promise<InventoryLocationObject | null> {
  const existing = await findInventoryLocation(db, tenantId, locationId)
  if (!existing) return null

  if (patch.isDefault) {
    await unsetDefaultLocation(db, tenantId)
  }

  const updateQuery = db
    .update(stockLocations)
    .set({
      ...(patch.name !== undefined ? { name: normalizeLocationName(patch.name) } : {}),
      ...(patch.code !== undefined ? { code: normalizeLocationCode(patch.code) } : {}),
      ...(patch.isDefault !== undefined ? { isDefault: patch.isDefault } : {}),
      ...(patch.isActive !== undefined ? { isActive: patch.isActive } : {}),
      updatedAt: new Date(),
    })
    .where(and(eq(stockLocations.tenantId, tenantId), eq(stockLocations.id, locationId)))

  const rows =
    typeof (updateQuery as { returning?: unknown }).returning === 'function'
      ? await (updateQuery as {
          returning: (shape: {
            id: typeof stockLocations.id
            name: typeof stockLocations.name
            code: typeof stockLocations.code
            isDefault: typeof stockLocations.isDefault
            isActive: typeof stockLocations.isActive
          }) => Promise<Array<{
            id: string
            name: string
            code: string | null
            isDefault: boolean
            isActive: boolean
          }>>
        }).returning({
          id: stockLocations.id,
          name: stockLocations.name,
          code: stockLocations.code,
          isDefault: stockLocations.isDefault,
          isActive: stockLocations.isActive,
        })
      : null

  return serializeLocation(Array.isArray(rows) ? rows[0] ?? null : null) ?? findInventoryLocation(db, tenantId, locationId)
}

export async function deleteInventoryLocation(
  db: Db,
  tenantId: string,
  locationId: string,
): Promise<boolean> {
  const existing = await findInventoryLocation(db, tenantId, locationId)
  if (!existing) return false
  if (existing.isDefault) {
    throw new InventoryLocationConflictError('Cannot delete default location')
  }

  const rows = await db
    .delete(stockLocations)
    .where(and(eq(stockLocations.tenantId, tenantId), eq(stockLocations.id, locationId)))
    .returning({ id: stockLocations.id })

  return rows.length > 0
}

export async function getInventorySettings(
  db: Db,
  tenantId: string,
): Promise<InventorySettingsObject> {
  const [row] = await db
    .select({
      inventoryMethod: tenantSettings.inventoryMethod,
      allowNegative: tenantSettings.allowNegative,
    })
    .from(tenantSettings)
    .where(eq(tenantSettings.tenantId, tenantId))
    .limit(1)

  return {
    inventory_method: row?.inventoryMethod ?? 'fifo',
    allow_negative: row?.allowNegative ?? true,
  }
}

export async function updateInventorySettings(
  db: Db,
  tenantId: string,
  patch: UpdateInventorySettingsInput,
): Promise<InventorySettingsObject> {
  const hasUpdates = patch.inventory_method !== undefined || patch.allow_negative !== undefined

  if (hasUpdates) {
    await db
      .update(tenantSettings)
      .set({
        ...(patch.inventory_method !== undefined && { inventoryMethod: patch.inventory_method }),
        ...(patch.allow_negative !== undefined && { allowNegative: patch.allow_negative }),
      })
      .where(eq(tenantSettings.tenantId, tenantId))
  }

  return getInventorySettings(db, tenantId)
}
