'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from '@/lib/supabase/server'
import { getAppOrg } from '@/lib/org'
import { getEffectiveUser } from '@/lib/effective-user'
import { canTransfer } from '@/lib/permissions'
import { defaultInvoiceDate, type InvoiceStatus } from '@/lib/invoices'

function fail(msg: string, retPath = '/invoices'): never {
  const params = new URLSearchParams({ error: msg })
  redirect(`${retPath}?${params.toString()}`)
}

function parseStatus(raw: FormDataEntryValue | null): InvoiceStatus {
  const s = String(raw ?? 'open').trim().toLowerCase()
  if (s === 'open' || s === 'sent' || s === 'paid') return s
  return 'open'
}

function parseMoneyOrNull(raw: FormDataEntryValue | null): number | null {
  if (raw == null) return null
  const s = String(raw).trim()
  if (s === '') return null
  const n = Number(s)
  if (!Number.isFinite(n)) return null
  return Math.round(n * 100) / 100
}

/**
 * Parse a YYYY-MM-DD invoice-date input. Returns null for empty or
 * malformed values (caller's responsibility to require non-null when
 * the field is required).
 */
function parseDateOrNull(raw: FormDataEntryValue | null): string | null {
  if (raw == null) return null
  const s = String(raw).trim()
  if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return null
  return s
}

/**
 * Create a new invoice. Owners only. Name + status; scope and notes
 * optional. Status transitions are also stamped here (sent_at, paid_at)
 * so the row carries its own timeline.
 */
export async function createInvoice(formData: FormData) {
  const supabase = await createClient()
  const org = await getAppOrg(supabase)
  if (!org) fail('Organization not found.')

  const eu = await getEffectiveUser(supabase, org.id)
  if (!canTransfer(eu)) fail('Only an owner can create invoices.')

  const name = String(formData.get('name') ?? '').trim()
  if (!name) fail('Invoice name is required.')

  const status = parseStatus(formData.get('status'))
  const operatorId =
    String(formData.get('operator_id') ?? '').trim() || null
  const clientId = String(formData.get('client_id') ?? '').trim() || null
  const manualTotal = parseMoneyOrNull(formData.get('manual_total_usd'))
  const notes = String(formData.get('notes') ?? '').trim() || null
  // Invoice date: user-facing accounting date. Falls back to the
  // last-day-of-previous-month default if the form sent nothing.
  const invoiceDate =
    parseDateOrNull(formData.get('invoice_date')) ?? defaultInvoiceDate()

  const now = new Date().toISOString()
  const { data: inserted, error } = await supabase
    .from('invoices')
    .insert({
      org_id: org.id,
      name,
      status,
      operator_id: operatorId,
      client_id: clientId,
      manual_total_usd: manualTotal,
      notes,
      invoice_date: invoiceDate,
      created_by: eu?.real_user_id ?? null,
      sent_at: status === 'sent' || status === 'paid' ? now : null,
      paid_at: status === 'paid' ? now : null,
    })
    .select('id')
    .single<{ id: string }>()
  if (error) {
    if (error.code === '23505')
      fail(`An invoice named "${name}" already exists.`)
    fail(error.message)
  }
  if (!inserted) fail('Could not create invoice.')

  revalidatePath('/invoices')
  redirect(`/invoices/${inserted.id}`)
}

/**
 * Update an existing invoice's fields. Status transitions auto-stamp
 * sent_at / paid_at the first time they fire (we don't overwrite an
 * existing timestamp if you toggle back and forth — auditing hates that).
 */
export async function updateInvoice(formData: FormData) {
  const id = String(formData.get('id') ?? '').trim()
  if (!id) fail('Missing invoice id.')

  const supabase = await createClient()
  const org = await getAppOrg(supabase)
  if (!org) fail('Organization not found.', `/invoices/${id}`)

  const eu = await getEffectiveUser(supabase, org.id)
  if (!canTransfer(eu)) {
    fail('Only an owner can edit invoices.', `/invoices/${id}`)
  }

  const { data: existing } = await supabase
    .from('invoices')
    .select('status, sent_at, paid_at')
    .eq('org_id', org.id)
    .eq('id', id)
    .maybeSingle<{
      status: InvoiceStatus
      sent_at: string | null
      paid_at: string | null
    }>()
  if (!existing) fail('Invoice not found.', `/invoices/${id}`)

  const name = String(formData.get('name') ?? '').trim()
  if (!name) fail('Invoice name is required.', `/invoices/${id}`)
  const newStatus = parseStatus(formData.get('status'))
  const operatorId =
    String(formData.get('operator_id') ?? '').trim() || null
  const clientId = String(formData.get('client_id') ?? '').trim() || null
  const manualTotal = parseMoneyOrNull(formData.get('manual_total_usd'))
  const notes = String(formData.get('notes') ?? '').trim() || null
  const invoiceDate = parseDateOrNull(formData.get('invoice_date'))

  const now = new Date().toISOString()
  const payload: Record<string, unknown> = {
    name,
    status: newStatus,
    operator_id: operatorId,
    client_id: clientId,
    manual_total_usd: manualTotal,
    notes,
    invoice_date: invoiceDate,
  }
  // Stamp sent_at / paid_at the FIRST time we cross into that state.
  if (
    (newStatus === 'sent' || newStatus === 'paid') &&
    existing.sent_at == null
  ) {
    payload.sent_at = now
  }
  if (newStatus === 'paid' && existing.paid_at == null) {
    payload.paid_at = now
  }

  const { error } = await supabase
    .from('invoices')
    .update(payload)
    .eq('org_id', org.id)
    .eq('id', id)
  if (error) {
    if (error.code === '23505')
      fail(`An invoice named "${name}" already exists.`, `/invoices/${id}`)
    fail(error.message, `/invoices/${id}`)
  }

  revalidatePath('/invoices')
  revalidatePath(`/invoices/${id}`)
  revalidatePath('/entries')
  redirect(`/invoices/${id}?info=Saved.`)
}

/**
 * Delete an invoice. Detaches every attached entry first (sets
 * invoice_id back to NULL) so the rows become unlocked again. Owners
 * only.
 */
export async function deleteInvoice(formData: FormData) {
  const id = String(formData.get('id') ?? '').trim()
  if (!id) fail('Missing invoice id.')

  const supabase = await createClient()
  const org = await getAppOrg(supabase)
  if (!org) fail('Organization not found.')

  const eu = await getEffectiveUser(supabase, org.id)
  if (!canTransfer(eu)) fail('Only an owner can delete invoices.')

  // Detach entries first (RLS / FK rule on time_entries.invoice_id is
  // ON DELETE SET NULL, but we do it explicitly to clear the audit
  // fields and revalidate the entry page).
  await supabase
    .from('time_entries')
    .update({
      invoice_id: null,
      invoice_applied_at: null,
      invoice_applied_by: null,
    })
    .eq('org_id', org.id)
    .eq('invoice_id', id)

  const { error } = await supabase
    .from('invoices')
    .delete()
    .eq('org_id', org.id)
    .eq('id', id)
  if (error) fail(error.message)

  revalidatePath('/invoices')
  revalidatePath('/entries')
  redirect('/invoices?info=Invoice+deleted.')
}

/**
 * Bulk-detach a set of entries from their invoices. Mirrors the old
 * bulkUnmarkTransferred shape. Owners only.
 */
export async function bulkRemoveFromInvoice(formData: FormData) {
  const supabase = await createClient()
  const org = await getAppOrg(supabase)
  if (!org) fail('Organization not found.')

  const eu = await getEffectiveUser(supabase, org.id)
  if (!canTransfer(eu)) fail('Only an owner can detach entries.')

  const invoiceId = String(formData.get('invoice_id') ?? '').trim() || null

  const ids = formData.getAll('id').map(String).filter(Boolean)
  if (ids.length === 0) fail('No entries selected.')

  const { data, error } = await supabase
    .from('time_entries')
    .update({
      invoice_id: null,
      invoice_applied_at: null,
      invoice_applied_by: null,
    })
    .eq('org_id', org.id)
    .in('id', ids)
    .not('invoice_id', 'is', null)
    .select('id')
  if (error) fail(error.message, invoiceId ? `/invoices/${invoiceId}` : '/entries')
  const count = data?.length ?? 0

  revalidatePath('/invoices')
  revalidatePath('/entries')
  if (invoiceId) revalidatePath(`/invoices/${invoiceId}`)

  const ret = invoiceId ? `/invoices/${invoiceId}` : '/entries'
  redirect(`${ret}?info=Detached+${count}+entries.`)
}
