/**
 * RecurringInvoiceAlarmDO — self-rescheduling daily recurring-invoice generator.
 *
 * Replaces the Cloudflare cron-trigger (disabled under the 5-cron account cap)
 * with a Durable Object alarm that re-arms itself for the next 06:00 UTC after
 * every run. The alarm chain is self-healing: alarm() ALWAYS reschedules in a
 * finally block, so a thrown generation never breaks the chain.
 *
 * Singleton: one instance, addressed by idFromName(RECURRING_ALARM_DO_NAME).
 *
 * Secret-gated fetch (x-zync-recurring-alarm + RECURRING_ALARM_SECRET):
 *   POST /ensure   — bootstrap/self-heal: arm the alarm if none is pending.
 *   POST /run-now  — run generation immediately (manual trigger); returns summary.
 */
import { DurableObject } from 'cloudflare:workers'
import { timingSafeEqual } from '@zync/auth'
import { createDb } from '@zync/db/queries'
import type { Env } from '@zync/types'
import { runRecurringInvoiceGeneration } from '../cron/recurring-invoice-generation'

/** Singleton DO name — bootstrap and any manual trigger MUST address this exact name. */
export const RECURRING_ALARM_DO_NAME = 'recurring-invoice-generator'

/** Next 06:00:00.000 UTC strictly after `fromMs`. */
function next6amUtc(fromMs: number): number {
  const d = new Date(fromMs)
  const next = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), 6, 0, 0, 0)
  return next <= fromMs ? next + 86_400_000 : next
}

function todayUtc(nowMs: number): string {
  return new Date(nowMs).toISOString().slice(0, 10) // YYYY-MM-DD (UTC)
}

export class RecurringInvoiceAlarmDO extends DurableObject<Env> {
  async alarm(): Promise<void> {
    const now = Date.now()
    try {
      const db = createDb(this.env)
      const today = todayUtc(now)
      const summary = await runRecurringInvoiceGeneration(db, today)
      console.log(
        `[RecurringInvoiceAlarmDO] run today=${today} processed=${summary.processed} generated=${summary.generated} skipped=${summary.skipped} errors=${summary.errors}`,
      )
    } catch (err) {
      console.error(`[RecurringInvoiceAlarmDO] generation threw: ${err}`)
    } finally {
      // ALWAYS re-arm so a thrown generation never breaks the alarm chain.
      await this.ctx.storage.setAlarm(next6amUtc(Date.now()))
    }
  }

  override async fetch(req: Request): Promise<Response> {
    const secret = this.env.RECURRING_ALARM_SECRET
    const header = req.headers.get('x-zync-recurring-alarm')
    if (!header || !secret || !timingSafeEqual(header, secret)) {
      return new Response(null, { status: 403 })
    }

    const url = new URL(req.url)
    if (req.method !== 'POST') {
      return new Response(null, { status: 405 })
    }

    if (url.pathname === '/ensure') {
      // Bootstrap / self-heal: arm only if no alarm is currently pending.
      const existing = await this.ctx.storage.getAlarm()
      if (existing === null) {
        const next = next6amUtc(Date.now())
        await this.ctx.storage.setAlarm(next)
        return Response.json({ ok: true, armed: true, nextRun: new Date(next).toISOString() })
      }
      return Response.json({ ok: true, armed: false, nextRun: new Date(existing).toISOString() })
    }

    if (url.pathname === '/run-now') {
      // Manual trigger — run immediately, leaving the scheduled alarm untouched.
      const db = createDb(this.env)
      const today = todayUtc(Date.now())
      const summary = await runRecurringInvoiceGeneration(db, today)
      return Response.json({ ok: true, today, ...summary })
    }

    return new Response(null, { status: 404 })
  }
}
