#!/usr/bin/env npx tsx
/**
 * Push Notification Scheduler
 *
 * Run this as a cron job every minute:
 *   * * * * * cd /path/to/app && npx tsx scripts/push-scheduler.ts
 *
 * Or via PM2:
 *   pm2 start scripts/push-scheduler.ts --interpreter="npx" --interpreter-args="tsx" --cron-restart="* * * * *" --no-autorestart
 *
 * What it does:
 * 1. Finds all users with notificationsEnabled=true
 * 2. For each user, checks cached calendar events with reminders
 * 3. If an event's reminder time matches "now" (within 1-minute window), sends a push notification
 * 4. Tracks sent notifications to avoid duplicates
 */

import { PrismaClient } from '@prisma/client';
import webpush from 'web-push';

const prisma = new PrismaClient();

const vapidPublicKey = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY;
const vapidPrivateKey = process.env.VAPID_PRIVATE_KEY;
const vapidSubject = process.env.VAPID_SUBJECT || 'mailto:admin@todo.martin-bierschenk.de';

if (!vapidPublicKey || !vapidPrivateKey) {
    console.error('[Push Scheduler] VAPID keys not configured. Set NEXT_PUBLIC_VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY in .env');
    process.exit(1);
}

webpush.setVapidDetails(vapidSubject, vapidPublicKey, vapidPrivateKey);

interface Reminder {
    method: string;
    minutes: number;
}

async function run() {
    const now = new Date();
    console.log(`[Push Scheduler] Running at ${now.toISOString()}`);

    // Find users with notifications enabled
    const users = await prisma.user.findMany({
        where: { notificationsEnabled: true },
        select: {
            id: true,
            timezone: true,
            pushSubscriptions: true,
        },
    });

    if (users.length === 0) {
        console.log('[Push Scheduler] No users with notifications enabled');
        await prisma.$disconnect();
        return;
    }

    console.log(`[Push Scheduler] Checking ${users.length} users`);

    for (const user of users) {
        if (user.pushSubscriptions.length === 0) continue;

        // Get upcoming events with reminders (next 7 days)
        const windowStart = new Date(now);
        const windowEnd = new Date(now);
        windowEnd.setDate(windowEnd.getDate() + 7);

        // Find events with reminders: both timed and all-day
        const events = await prisma.cachedCalendarEvent.findMany({
            where: {
                userId: user.id,
                reminders: { not: null },
                OR: [
                    { startDateTime: { gte: now, lte: windowEnd } },
                    { startDate: { not: null } },
                ],
            },
            select: {
                externalId: true,
                title: true,
                startDateTime: true,
                startDate: true,
                reminders: true,
                calendarTitle: true,
            },
        });

        console.log(`[Push Scheduler] Found ${events.length} events with reminders for user ${user.id}`);

        for (const event of events) {
            // Determine the event start time
            let eventStart: Date | null = event.startDateTime;
            if (!eventStart && event.startDate) {
                // All-day events: treat as starting at 09:00 local time (user timezone)
                // Parse the date string and set to 09:00 UTC as approximation
                // (for CET/CEST this means ~10:00/11:00 local, close enough for day-before reminders)
                eventStart = new Date(event.startDate + 'T07:00:00.000Z');
            }
            if (!eventStart || !event.reminders) continue;

            // Skip events that already passed
            const maxReminderMinutes = 10080; // 1 week
            if (eventStart.getTime() + maxReminderMinutes * 60 * 1000 < now.getTime()) continue;

            let reminders: Reminder[] = [];
            try {
                reminders = event.reminders as unknown as Reminder[];
                if (!Array.isArray(reminders)) continue;
            } catch {
                continue;
            }

            for (const reminder of reminders) {
                if (reminder.minutes < 0) continue;

                // Calculate when notification should fire
                const notifyAt = new Date(eventStart.getTime() - reminder.minutes * 60 * 1000);

                // Check if we're within the 1-minute window
                const diffMs = Math.abs(now.getTime() - notifyAt.getTime());
                if (diffMs > 60 * 1000) continue; // Outside window

                // Check if already sent
                const alreadySent = await prisma.sentNotification.findUnique({
                    where: {
                        userId_eventId_minutes: {
                            userId: user.id,
                            eventId: event.externalId,
                            minutes: reminder.minutes,
                        },
                    },
                });

                if (alreadySent) continue;

                // Build notification payload
                const timeStr = eventStart.toLocaleTimeString('en-US', {
                    hour: '2-digit',
                    minute: '2-digit',
                    hour12: false,
                });
                const body = reminder.minutes === 0
                    ? `Starting now at ${timeStr}`
                    : reminder.minutes < 60
                        ? `In ${reminder.minutes} minutes (${timeStr})`
                        : reminder.minutes < 1440
                            ? `In ${Math.round(reminder.minutes / 60)} hour(s) (${timeStr})`
                            : `In ${Math.round(reminder.minutes / 1440)} day(s) (${timeStr})`;

                const payload = JSON.stringify({
                    title: event.title || 'Calendar Event',
                    body,
                    icon: '/icons/icon-192.png',
                    url: '/tasks',
                    eventId: event.externalId,
                });

                // Send to all user's subscriptions
                let sentOk = false;
                for (const sub of user.pushSubscriptions) {
                    try {
                        await webpush.sendNotification(
                            {
                                endpoint: sub.endpoint,
                                keys: { p256dh: sub.p256dh, auth: sub.auth },
                            },
                            payload
                        );
                        sentOk = true;
                        console.log(`[Push Scheduler] Sent: "${event.title}" to user ${user.id}`);
                    } catch (err: any) {
                        if (err.statusCode === 410 || err.statusCode === 404) {
                            // Subscription expired, remove it
                            await prisma.pushSubscription.delete({ where: { id: sub.id } }).catch(() => { });
                            console.log(`[Push Scheduler] Removed stale subscription ${sub.id}`);
                        } else {
                            console.error(`[Push Scheduler] Send failed:`, err.message);
                        }
                    }
                }

                // Record as sent
                if (sentOk) {
                    await prisma.sentNotification.create({
                        data: {
                            userId: user.id,
                            eventId: event.externalId,
                            minutes: reminder.minutes,
                        },
                    });
                }
            }
        }
    }

    // Cleanup old sent notifications (older than 7 days)
    const weekAgo = new Date(now);
    weekAgo.setDate(weekAgo.getDate() - 7);
    const deleted = await prisma.sentNotification.deleteMany({
        where: { sentAt: { lt: weekAgo } },
    });
    if (deleted.count > 0) {
        console.log(`[Push Scheduler] Cleaned up ${deleted.count} old sent notifications`);
    }

    await prisma.$disconnect();
    console.log('[Push Scheduler] Done');
}

run().catch((err) => {
    console.error('[Push Scheduler] Fatal error:', err);
    prisma.$disconnect();
    process.exit(1);
});
