'use server'

import { db } from '@/lib/db'
import { requireSession } from '@/lib/auth'
import { saveItemImage, saveVoiceNote, deleteUpload } from '@/lib/images'
import { rebuildSearchText } from '@/lib/search'
import { enqueueIdentification } from '@/lib/identify'
import { maybeAutoStartSession } from '@/lib/ai-review-session'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'

function parseTags(raw: string | null): string[] {
  if (!raw) return []
  return Array.from(
    new Set(
      raw
        .split(/[,\n]/)
        .map(t => t.trim().toLowerCase())
        .filter(t => t.length > 0 && t.length <= 40),
    ),
  ).slice(0, 20)
}

export async function createItem(_prev: unknown, formData: FormData) {
  const session = await requireSession()

  const name = (formData.get('name') as string)?.trim()
  const locationId = formData.get('locationId') as string
  const description = (formData.get('description') as string)?.trim() || null
  const category = (formData.get('category') as string)?.trim() || null
  const brand = (formData.get('brand') as string)?.trim() || null
  const serialNumber = (formData.get('serialNumber') as string)?.trim() || null
  const notes = (formData.get('notes') as string)?.trim() || null
  const caption = (formData.get('caption') as string)?.trim() || null
  const qtyRaw = formData.get('qty') as string
  const qty = qtyRaw ? Math.max(1, parseInt(qtyRaw, 10) || 1) : 1
  const valueRaw = formData.get('value') as string
  const value = valueRaw ? parseFloat(valueRaw) : null
  const tagNames = parseTags(formData.get('tags') as string | null)
  const photo = formData.get('photo') as File | null

  if (!locationId) return { error: 'Location is required' }
  if (!name && !photo) return { error: 'Provide a name or a photo' }

  const photoBuffer =
    photo && photo.size > 0
      ? Buffer.from(await photo.arrayBuffer())
      : null

  const saved = photoBuffer ? await saveItemImage(photoBuffer) : null

  try {
    const item = await db.$transaction(async tx => {
      const created = await tx.item.create({
        data: {
          name: name || 'Untitled item',
          description,
          category,
          brand,
          serialNumber,
          notes,
          caption,
          qty,
          value: value != null && !isNaN(value) ? value : null,
          locationId,
          addedById: session.userId,
          tags: tagNames.length
            ? {
                connectOrCreate: tagNames.map(n => ({
                  where: { name: n },
                  create: { name: n },
                })),
              }
            : undefined,
        },
      })
      if (saved) {
        const photo = await tx.photo.create({
          data: {
            itemId: created.id,
            path: saved.path,
            thumbPath: saved.thumbPath,
            width: saved.width,
            height: saved.height,
            bytes: saved.bytes,
            takenAt: saved.takenAt,
            isPrimary: true,
          },
        })
        await rebuildSearchText(tx, created.id)
        return { id: created.id, photoId: photo.id }
      }
      await rebuildSearchText(tx, created.id)
      return { id: created.id, photoId: null as string | null }
    })

    if (item.photoId) {
      await enqueueIdentification(item.photoId)
    }

    revalidatePath('/')
    redirect(`/items/${item.id}`)
  } catch (err) {
    if (saved) {
      await deleteUpload(saved.path)
      await deleteUpload(saved.thumbPath)
    }
    throw err
  }
}

export async function updateItem(_prev: unknown, formData: FormData) {
  await requireSession()
  const id = formData.get('id') as string
  if (!id) return { error: 'Missing id' }

  const name = (formData.get('name') as string)?.trim() || 'Untitled item'
  const locationId = formData.get('locationId') as string
  const description = (formData.get('description') as string)?.trim() || null
  const category = (formData.get('category') as string)?.trim() || null
  const brand = (formData.get('brand') as string)?.trim() || null
  const serialNumber = (formData.get('serialNumber') as string)?.trim() || null
  const notes = (formData.get('notes') as string)?.trim() || null
  const caption = (formData.get('caption') as string)?.trim() || null
  const qtyRaw = formData.get('qty') as string
  const qty = qtyRaw ? Math.max(1, parseInt(qtyRaw, 10) || 1) : 1
  const valueRaw = formData.get('value') as string
  const value = valueRaw ? parseFloat(valueRaw) : null
  const status = (formData.get('status') as string) || 'ACTIVE'
  const tagNames = parseTags(formData.get('tags') as string | null)

  if (!locationId) return { error: 'Location is required' }

  await db.$transaction(async tx => {
    await tx.item.update({
      where: { id },
      data: {
        name,
        description,
        category,
        brand,
        serialNumber,
        notes,
        caption,
        qty,
        value: value != null && !isNaN(value) ? value : null,
        locationId,
        status: status as 'ACTIVE' | 'LOANED' | 'DISPOSED' | 'LOST',
        tags: {
          set: [],
          connectOrCreate: tagNames.map(n => ({
            where: { name: n },
            create: { name: n },
          })),
        },
      },
    })
    await rebuildSearchText(tx, id)
  })

  revalidatePath('/')
  revalidatePath(`/items/${id}`)
  redirect(`/items/${id}`)
}

export async function setPendingNote(itemId: string, note: string) {
  await requireSession()
  const trimmed = note.trim()
  await db.item.update({
    where: { id: itemId },
    data: {
      pendingNote: trimmed || null,
      // Any non-empty note coming in post-capture means the user wants
      // another look. An empty note (clear) shouldn't trigger a review.
      ...(trimmed ? { needsAIReview: true } : {}),
    },
  })
  if (trimmed) void maybeAutoStartSession()
  revalidatePath(`/items/${itemId}`)
}

export async function setVoiceNote(formData: FormData) {
  await requireSession()
  const itemId = formData.get('itemId') as string
  const voice = formData.get('voice') as File | null
  const voiceMime = (formData.get('voiceMime') as string) || 'audio/webm'
  if (!itemId) return { ok: false, error: 'Missing itemId' as const }
  if (!voice || voice.size === 0) {
    return { ok: false, error: 'No audio' as const }
  }

  const prior = await db.item.findUnique({
    where: { id: itemId },
    select: { voiceNotePath: true },
  })

  const buf = Buffer.from(await voice.arrayBuffer())
  const saved = await saveVoiceNote(buf, voiceMime)

  await db.item.update({
    where: { id: itemId },
    data: {
      voiceNotePath: saved.path,
      voiceNoteMime: saved.mime,
      needsAIReview: true,
    },
  })

  if (prior?.voiceNotePath) {
    await deleteUpload(prior.voiceNotePath)
  }

  void maybeAutoStartSession()
  revalidatePath(`/items/${itemId}`)
  return { ok: true as const, path: saved.path, mime: saved.mime }
}

export async function deleteVoiceNote(itemId: string) {
  await requireSession()
  const item = await db.item.findUnique({
    where: { id: itemId },
    select: { voiceNotePath: true },
  })
  if (!item) return
  if (item.voiceNotePath) {
    await deleteUpload(item.voiceNotePath)
  }
  await db.item.update({
    where: { id: itemId },
    data: { voiceNotePath: null, voiceNoteMime: null },
  })
  revalidatePath(`/items/${itemId}`)
}

// The Phase 2 identify pass can split one capture into multiple items by
// inserting extra Photo rows that point at the same file on disk. Only
// unlink a file when no other Photo row still references it.
async function unlinkPhotoFilesIfOrphaned(path: string, thumbPath: string) {
  const [pathStillUsed, thumbStillUsed] = await Promise.all([
    db.photo.count({ where: { path } }),
    db.photo.count({ where: { thumbPath } }),
  ])
  if (pathStillUsed === 0) await deleteUpload(path)
  if (thumbStillUsed === 0) await deleteUpload(thumbPath)
}

export async function deleteItem(id: string) {
  await requireSession()
  const photos = await db.photo.findMany({ where: { itemId: id } })
  await db.item.delete({ where: { id } })
  for (const p of photos) {
    await unlinkPhotoFilesIfOrphaned(p.path, p.thumbPath)
  }
  revalidatePath('/')
  redirect('/')
}

export async function addPhoto(formData: FormData) {
  await requireSession()
  const itemId = formData.get('itemId') as string
  const photo = formData.get('photo') as File | null
  if (!itemId || !photo || photo.size === 0) return

  const buf = Buffer.from(await photo.arrayBuffer())
  const saved = await saveItemImage(buf)

  await db.$transaction(async tx => {
    const exists = await tx.photo.count({ where: { itemId } })
    await tx.photo.create({
      data: {
        itemId,
        path: saved.path,
        thumbPath: saved.thumbPath,
        width: saved.width,
        height: saved.height,
        bytes: saved.bytes,
        takenAt: saved.takenAt,
        isPrimary: exists === 0,
        position: exists, // append at the end of the gallery
      },
    })
  })

  revalidatePath(`/items/${itemId}`)
}

export async function setPrimaryPhoto(itemId: string, photoId: string) {
  await requireSession()
  await db.$transaction([
    db.photo.updateMany({ where: { itemId }, data: { isPrimary: false } }),
    db.photo.update({ where: { id: photoId }, data: { isPrimary: true } }),
  ])
  revalidatePath(`/items/${itemId}`)
}

export async function reorderPhotos(itemId: string, photoIds: string[]) {
  await requireSession()
  const existing = await db.photo.findMany({ where: { itemId }, select: { id: true } })
  const valid = new Set(existing.map(p => p.id))
  // Reject stale orderings (e.g. a photo was added/deleted on another device
  // since the client rendered) rather than half-applying them.
  if (photoIds.length !== existing.length || !photoIds.every(id => valid.has(id))) return
  await db.$transaction(
    photoIds.map((id, position) =>
      db.photo.update({ where: { id }, data: { position } }),
    ),
  )
  revalidatePath(`/items/${itemId}`)
}

export async function deletePhoto(itemId: string, photoId: string) {
  await requireSession()
  const photo = await db.photo.findUnique({ where: { id: photoId } })
  if (!photo || photo.itemId !== itemId) return
  await db.photo.delete({ where: { id: photoId } })
  await unlinkPhotoFilesIfOrphaned(photo.path, photo.thumbPath)
  revalidatePath(`/items/${itemId}`)
}
