/**
 * P1-10 — report dispatcher.
 *
 * Glues scheduler + builder + email service together. The worker tick
 * calls `dispatchDueReports(prisma)` periodically (hourly); only rows
 * whose interval has elapsed trigger a send.
 *
 * Persists `lastSentAt` BEFORE the send completes so a crash between
 * "send" and "update" doesn't fire duplicate reports on the next tick.
 * If the send itself fails the row is rolled back via try/catch — log
 * warning, no retry on this tick (next tick will pick it up again
 * since lastSentAt was rolled back).
 */
import type { PrismaClient } from '@prisma/client';
import { shouldSendReportNow } from './report-scheduler';
import { buildScheduledReport } from './report-builder';
import { log } from '@/lib/observability/logger';

export interface DispatchResult {
    inspected: number;
    sent: number;
    failed: number;
    skipped: number;
}

export async function dispatchDueReports(
    prisma: PrismaClient,
    now: Date = new Date()
): Promise<DispatchResult> {
    const result: DispatchResult = { inspected: 0, sent: 0, failed: 0, skipped: 0 };

    const prefs = await prisma.reportPreference.findMany({
        where: { enabled: true },
        include: { user: { select: { id: true, email: true, name: true } } },
    });
    result.inspected = prefs.length;

    for (const pref of prefs) {
        if (!shouldSendReportNow(pref, now)) {
            result.skipped++;
            continue;
        }
        if (!pref.user?.email) {
            log.warn({ prefId: pref.id }, 'ReportPreference enabled but user has no email — skipping');
            result.skipped++;
            continue;
        }

        // Reserve the slot first. If the send below fails we restore
        // lastSentAt so the next tick retries; otherwise the slot is
        // claimed and we cannot double-fire even if the worker crashes
        // between the send call and the success path.
        const previousLastSentAt = pref.lastSentAt;
        await prisma.reportPreference.update({
            where: { id: pref.id },
            data: { lastSentAt: now },
        });

        try {
            const built = await buildScheduledReport(prisma, pref.userId, pref.frequency, now);
            if (!built) {
                throw new Error(`unknown frequency: ${pref.frequency}`);
            }

            // Lazy-import EmailService so this module doesn't pull
            // SMTP/nodemailer at module-eval time. Keeps test imports
            // cheap and lets us swap impls for unit tests.
            const { EmailService } = await import('./email.service');
            const sendResult = await EmailService.sendRaw(
                pref.user.email,
                built.subject,
                built.html
            );
            if (!sendResult.success) {
                throw new Error(sendResult.error || 'send failed');
            }

            result.sent++;
            log.info(
                {
                    prefId: pref.id,
                    userId: pref.userId,
                    frequency: pref.frequency,
                    monitors: built.summary.monitors,
                    incidentsInPeriod: built.summary.incidentsInPeriod,
                },
                'scheduled report sent'
            );
        } catch (err) {
            // Rollback the slot so the next tick retries.
            await prisma.reportPreference.update({
                where: { id: pref.id },
                data: { lastSentAt: previousLastSentAt },
            }).catch(() => { /* if even rollback fails, give up */ });
            result.failed++;
            log.error(
                { prefId: pref.id, userId: pref.userId, err },
                'scheduled report send failed; will retry on next tick'
            );
        }
    }

    return result;
}
