import CheckinTask from '#models/checkin_task'
import Invoice from '#models/invoice'
import { getQueue } from '#services/queue'
import { generateCheckinToken } from '#services/checkin_token'
import * as clock from '#services/clock'
import app from '@adonisjs/core/services/app'
import type { TransactionClientContract } from '@adonisjs/lucid/types/database'

const CHECKIN_QUEUE = 'checkins'

function shouldEnqueue(): boolean {
  return app.getEnvironment() !== 'test'
}

/**
 * Programme un check-in pour une facture.
 *
 * V1 : 1 check-in par facture, envoyé à `dueDate` (pile à l'échéance).
 * Si dueDate est dans le passé → envoie immédiat (à `now + 1min`),
 * pour que les factures importées en retard reçoivent quand même un
 * check-in.
 *
 * Le token est généré ici (plain) — on retourne le plain pour permettre
 * au caller de le passer dans des emails de test si besoin, mais en
 * pratique seul le hash est stocké et lu via SendCheckinJob.
 *
 * Idempotent par invoice : si une CheckinTask `scheduled` existe déjà,
 * on la cancelle d'abord puis on en crée une nouvelle (cas re-scheduling
 * après changement de dueDate).
 *
 * En tests : la task DB est créée mais l'enqueue BullMQ est skippé
 * (les tx auto-rollback laisseraient des jobs orphelins en Redis sinon).
 */
export async function scheduleCheckinForInvoice(
  invoice: Invoice,
  trx?: TransactionClientContract
): Promise<{ task: CheckinTask; plain: string } | null> {
  // Cancel l'éventuelle CheckinTask scheduled précédente.
  const existing = await CheckinTask.query(trx ? { client: trx } : undefined)
    .where('invoice_id', invoice.id)
    .where('status', 'scheduled')
  const queue = shouldEnqueue() ? getQueue(CHECKIN_QUEUE) : null
  for (const t of existing) {
    if (queue) await queue.remove(`checkin-${t.id}`).catch(() => {})
    t.useTransaction(trx ?? (null as never))
    t.status = 'expired'
    await t.save()
  }

  const now = await clock.now(invoice.organizationId)
  const sendAtRaw = invoice.dueDate
  const sendAt = sendAtRaw < now ? now.plus({ minutes: 1 }) : sendAtRaw

  const { plain, hashed } = generateCheckinToken()

  const task = await CheckinTask.create(
    {
      organizationId: invoice.organizationId,
      invoiceId: invoice.id,
      sendAt,
      tokenHash: hashed,
      status: 'scheduled',
      sentAt: null,
      answeredAt: null,
      answer: null,
    },
    trx ? { client: trx } : undefined
  )

  if (queue) {
    const delay = Math.max(0, sendAt.toMillis() - now.toMillis())
    await queue.add(
      'send-checkin',
      { taskId: task.id, plain },
      {
        delay,
        jobId: `checkin-${task.id}`,
        attempts: 3,
        backoff: { type: 'exponential', delay: 30_000 },
      }
    )
  }

  return { task, plain }
}

/**
 * Annule le check-in scheduled d'une facture (appelé par mark-paid).
 */
export async function cancelCheckinForInvoice(
  invoiceId: string,
  trx?: TransactionClientContract
): Promise<void> {
  const tasks = await CheckinTask.query(trx ? { client: trx } : undefined)
    .where('invoice_id', invoiceId)
    .where('status', 'scheduled')
  if (tasks.length === 0) return

  const queue = shouldEnqueue() ? getQueue(CHECKIN_QUEUE) : null
  for (const t of tasks) {
    if (queue) await queue.remove(`checkin-${t.id}`).catch(() => {})
    t.useTransaction(trx ?? (null as never))
    t.status = 'expired'
    await t.save()
  }
}
