import { randomUUID } from 'node:crypto';
import { Router } from 'express';
import { z } from 'zod';
import { requireAdmin, requireAuth, type AuthenticatedRequest } from './auth';
import { pool } from './db';

export const notificationTypes = [
  'engagement_request', 'engagement_decision', 'photo_request', 'photo_decision',
  'meeting_proposed', 'meeting_confirmed', 'meeting_cancelled', 'schedule_confirmed', 'call_ready',
  'support_appointment', 'support_updated', 'payment_reviewed', 'guardian_reviewed',
  'journey_certificate',
] as const;
export type NotificationType = typeof notificationTypes[number];

interface NotificationPayload {
  type: NotificationType;
  title: string;
  body: string;
  journeyId?: string;
  requestId?: string;
  supportRequestId?: string;
  dedupeKey: string;
}

export async function notifyUsers(userIds: string[], payload: NotificationPayload): Promise<void> {
  const delivery = await pool.query<{ enabled: boolean; delay_minutes: number }>(
    'SELECT enabled,delay_minutes FROM notification_settings WHERE type=$1', [payload.type],
  );
  if (delivery.rows[0]?.enabled === false) return;
  const delayMinutes = delivery.rows[0]?.delay_minutes ?? 0;
  const uniqueIds = [...new Set(userIds)];
  await Promise.all(uniqueIds.map((userId) => pool.query(
    `INSERT INTO user_notifications
       (id,user_id,type,title,body,request_id,journey_id,support_request_id,available_at,dedupe_key)
     VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW()+($9*INTERVAL '1 minute'),$10)
     ON CONFLICT (user_id,dedupe_key) DO NOTHING`,
    [randomUUID(), userId, payload.type, payload.title, payload.body,
      payload.requestId ?? null, payload.journeyId ?? null,
      payload.supportRequestId ?? null, delayMinutes, payload.dedupeKey],
  )));
}

export async function journeyNotificationRecipients(
  journeyId: string,
  excludedUserId?: string,
): Promise<{ userIds: string[]; requestId: string | null }> {
  const result = await pool.query<{ user_id: string; request_id: string | null }>(
    `SELECT DISTINCT people.user_id, journey.request_id
     FROM journeys journey
     CROSS JOIN LATERAL (
       SELECT journey.first_user_id AS user_id
       UNION SELECT journey.second_user_id
       UNION SELECT guardian_user_id FROM journey_call_guardians WHERE journey_id=journey.id
     ) people
     JOIN users account ON account.id=people.user_id AND account.is_active
     WHERE journey.id=$1 AND ($2::uuid IS NULL OR people.user_id<>$2)`,
    [journeyId, excludedUserId ?? null],
  );
  return { userIds: result.rows.map((row) => row.user_id), requestId: result.rows[0]?.request_id ?? null };
}

export const notificationsRouter = Router();

notificationsRouter.get('/me/notifications', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    const result = await pool.query(
      `SELECT id,type,title,body,request_id AS "requestId",journey_id AS "journeyId",
              support_request_id AS "supportRequestId",
              read_at AS "readAt",created_at AS "createdAt"
       FROM user_notifications WHERE user_id=$1 AND available_at<=NOW()
       ORDER BY created_at DESC LIMIT 100`,
      [request.authUser!.id],
    );
    response.json({ notifications: result.rows });
  });

notificationsRouter.post('/me/notifications/:id/read', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    const id = z.uuid().safeParse(request.params.id);
    if (!id.success) { response.status(400).json({ error: 'Invalid notification' }); return; }
    const result = await pool.query(
      `UPDATE user_notifications SET read_at=COALESCE(read_at,NOW())
       WHERE id=$1 AND user_id=$2 RETURNING id`,
      [id.data, request.authUser!.id],
    );
    if (!result.rowCount) { response.status(404).json({ error: 'Notification not found' }); return; }
    response.json({ read: true });
  });

notificationsRouter.delete('/me/notifications/:id', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    const id = z.uuid().safeParse(request.params.id);
    if (!id.success) { response.status(400).json({ error: 'Invalid notification' }); return; }
    await pool.query('DELETE FROM user_notifications WHERE id=$1 AND user_id=$2', [id.data, request.authUser!.id]);
    response.json({ deleted: true });
  });

notificationsRouter.delete('/me/notifications', requireAuth,
  async (request: AuthenticatedRequest, response) => {
    const result = await pool.query(
      'DELETE FROM user_notifications WHERE user_id=$1 AND available_at<=NOW()', [request.authUser!.id],
    );
    response.json({ deletedCount: result.rowCount ?? 0 });
  });

notificationsRouter.get('/admin/notification-settings', requireAuth, requireAdmin,
  async (_request, response) => {
    const result = await pool.query(
      `SELECT type,enabled,delay_minutes AS "delayMinutes",updated_at AS "updatedAt"
       FROM notification_settings ORDER BY type`,
    );
    response.json({ settings: result.rows });
  });

notificationsRouter.put('/admin/notification-settings', requireAuth, requireAdmin,
  async (request: AuthenticatedRequest, response) => {
    const parsed = z.object({ settings: z.array(z.object({
      type: z.enum(notificationTypes), enabled: z.boolean(),
      delayMinutes: z.number().int().min(0).max(1440),
    })).length(notificationTypes.length) }).safeParse(request.body);
    if (!parsed.success) { response.status(400).json({ error: 'Invalid notification settings' }); return; }
    const client = await pool.connect();
    try {
      await client.query('BEGIN');
      for (const item of parsed.data.settings) {
        await client.query(
          `INSERT INTO notification_settings (type,enabled,delay_minutes,updated_by)
           VALUES ($1,$2,$3,$4) ON CONFLICT (type) DO UPDATE SET
           enabled=EXCLUDED.enabled,delay_minutes=EXCLUDED.delay_minutes,
           updated_by=EXCLUDED.updated_by,updated_at=NOW()`,
          [item.type, item.enabled, item.delayMinutes, request.authUser!.id],
        );
      }
      await client.query(
        `INSERT INTO audit_events (user_id,event_type,details)
         VALUES ($1,'notification_settings_updated',$2::jsonb)`,
        [request.authUser!.id, JSON.stringify({ count: parsed.data.settings.length })],
      );
      await client.query('COMMIT');
      response.json({ saved: true });
    } catch (error) { await client.query('ROLLBACK'); throw error; }
    finally { client.release(); }
  });
