/**
 * notifications-svc — Web Push, Email (SMTP), напоминания
 * docs/architecture: 01-perplexity-analogue-design.md §5.13, 06-roadmap-specification §1.6
 * API: POST /api/v1/notifications/subscribe, preferences, remind, send-email
 */

import Fastify from 'fastify';
import cors from '@fastify/cors';
import webpush from 'web-push';
import { eq, and, lte, isNull } from 'drizzle-orm';
import { db } from './db/index.js';
import {
  pushSubscriptions,
  notificationPreferences,
  reminders,
} from './db/schema.js';
import { isSmtpConfigured, sendEmail } from './lib/email.js';

const PORT = parseInt(process.env.PORT ?? '3013', 10);
const AUTH_SERVICE_URL = process.env.AUTH_SERVICE_URL ?? '';
const VAPID_PUBLIC_KEY = process.env.VAPID_PUBLIC_KEY ?? '';
const VAPID_PRIVATE_KEY = process.env.VAPID_PRIVATE_KEY ?? '';
const INTERNAL_API_KEY = process.env.INTERNAL_API_KEY ?? '';

const app = Fastify({ logger: true });

await app.register(cors, { origin: true });

let vapidPublic = VAPID_PUBLIC_KEY;
let vapidPrivate = VAPID_PRIVATE_KEY;

if (!vapidPublic || !vapidPrivate) {
  const keys = webpush.generateVAPIDKeys();
  vapidPublic = keys.publicKey;
  vapidPrivate = keys.privateKey;
  app.log.warn(
    'VAPID keys not set. Using generated keys (not suitable for production). Set VAPID_PUBLIC_KEY and VAPID_PRIVATE_KEY.'
  );
}
webpush.setVapidDetails(
  'mailto:notifications@gooseek.local',
  vapidPublic,
  vapidPrivate
);

if (!isSmtpConfigured()) {
  app.log.warn(
    'SMTP not configured. Email sending disabled. Set SMTP_URL (e.g. smtp://user:pass@smtp.example:587)'
  );
}

async function getUserIdFromToken(
  authHeader: string | undefined
): Promise<string | null> {
  if (!authHeader?.startsWith('Bearer ')) return null;
  const token = authHeader.slice(7);
  if (!token) return null;
  if (!AUTH_SERVICE_URL) {
    return 'guest';
  }
  try {
    const res = await fetch(
      `${AUTH_SERVICE_URL.replace(/\/$/, '')}/api/auth/me`,
      {
        headers: { Authorization: `Bearer ${token}` },
        signal: AbortSignal.timeout(5000),
      }
    );
    if (!res.ok) return null;
    const data = (await res.json()) as { id?: string };
    return data.id ?? null;
  } catch {
    return null;
  }
}

async function sendPushToUser(
  userId: string,
  payload: { title?: string; body?: string; [k: string]: unknown }
): Promise<void> {
  const subs = await db
    .select()
    .from(pushSubscriptions)
    .where(eq(pushSubscriptions.userId, userId));
  const textPayload = JSON.stringify(payload);
  for (const sub of subs) {
    try {
      await webpush.sendNotification(
        {
          endpoint: sub.endpoint,
          keys: { p256dh: sub.p256dh, auth: sub.auth },
        },
        textPayload,
        { TTL: 86400 }
      );
    } catch (err) {
      app.log.warn({ err, subscriptionId: sub.id }, 'Push send failed');
      if ((err as { statusCode?: number }).statusCode === 410 || (err as { statusCode?: number }).statusCode === 404) {
        await db.delete(pushSubscriptions).where(eq(pushSubscriptions.id, sub.id));
      }
    }
  }
}

function startReminderWorker(): void {
  const intervalMs = 60_000;
  setInterval(async () => {
    try {
      const due = await db
        .select()
        .from(reminders)
        .where(and(lte(reminders.remindAt, new Date()), isNull(reminders.sentAt)));
      for (const r of due) {
        await sendPushToUser(r.userId, {
          title: r.query ? `Reminder: ${r.query.slice(0, 80)}` : 'GooSeek Reminder',
          body: r.threadId ? `Thread ${r.threadId}` : 'Your reminder',
          type: 'reminder',
          threadId: r.threadId,
        });
        await db
          .update(reminders)
          .set({ sentAt: new Date() })
          .where(eq(reminders.id, r.id));
      }
    } catch (err) {
      app.log.error({ err }, 'Reminder worker error');
    }
  }, intervalMs);
}

app.get('/health', async () => ({
  status: 'ok',
  smtp: isSmtpConfigured(),
}));
app.get('/ready', async () => ({ status: 'ready' }));

app.get('/api/v1/notifications/vapid-public', async () => ({
  vapidPublicKey: vapidPublic,
}));

app.post<{
  Body: { endpoint?: string; keys?: { p256dh?: string; auth?: string } };
}>('/api/v1/notifications/subscribe', async (req, reply) => {
  const userId = await getUserIdFromToken(req.headers.authorization);
  if (!userId) {
    return reply.status(401).send({ error: 'Authorization required' });
  }
  const { endpoint, keys } = req.body ?? {};
  if (!endpoint?.trim() || !keys?.p256dh?.trim() || !keys?.auth?.trim()) {
    return reply.status(400).send({
      error: 'endpoint and keys.p256dh, keys.auth are required',
    });
  }
  const id = crypto.randomUUID();
  try {
    await db.insert(pushSubscriptions).values({
      id,
      userId,
      endpoint: endpoint.trim(),
      p256dh: keys.p256dh.trim(),
      auth: keys.auth.trim(),
    });
    return reply.status(201).send({ id });
  } catch (err) {
    req.log.error(err);
    return reply.status(500).send({ error: 'Failed to save subscription' });
  }
});

app.post<{
  Body: {
    pushOnAnswer?: boolean;
    emailOnDeepResearch?: boolean;
    reminders?: unknown[];
  };
}>('/api/v1/notifications/preferences', async (req, reply) => {
  const userId = await getUserIdFromToken(req.headers.authorization);
  if (!userId) {
    return reply.status(401).send({ error: 'Authorization required' });
  }
  const { pushOnAnswer, emailOnDeepResearch, reminders: rems } = req.body ?? {};
  const pushVal = pushOnAnswer ?? true;
  const emailVal = emailOnDeepResearch ?? false;
  const remsVal = Array.isArray(rems) ? rems : [];
  try {
    await db
      .insert(notificationPreferences)
      .values({
        userId,
        pushOnAnswer: pushVal,
        emailOnDeepResearch: emailVal,
        reminders: remsVal,
      })
      .onConflictDoUpdate({
        target: notificationPreferences.userId,
        set: {
          pushOnAnswer: pushVal,
          emailOnDeepResearch: emailVal,
          reminders: remsVal,
          updatedAt: new Date(),
        },
      });
    return reply.status(200).send({ ok: true });
  } catch (err) {
    req.log.error(err);
    return reply.status(500).send({ error: 'Failed to save preferences' });
  }
});

app.post<{
  Body: { threadId?: string; query?: string; remindAt?: string };
}>('/api/v1/notifications/remind', async (req, reply) => {
  const userId = await getUserIdFromToken(req.headers.authorization);
  if (!userId) {
    return reply.status(401).send({ error: 'Authorization required' });
  }
  const { threadId, query, remindAt } = req.body ?? {};
  if (!remindAt) {
    return reply.status(400).send({ error: 'remindAt (ISO8601) is required' });
  }
  const at = new Date(remindAt);
  if (isNaN(at.getTime())) {
    return reply.status(400).send({ error: 'remindAt must be valid ISO8601' });
  }
  if (at <= new Date()) {
    return reply.status(400).send({ error: 'remindAt must be in the future' });
  }
  const id = crypto.randomUUID();
  try {
    await db.insert(reminders).values({
      id,
      userId,
      threadId: threadId ?? null,
      query: query ?? null,
      remindAt: at,
    });
    return reply.status(201).send({
      id,
      remindAt: at.toISOString(),
    });
  } catch (err) {
    req.log.error(err);
    return reply.status(500).send({ error: 'Failed to create reminder' });
  }
});

function checkInternalAuth(req: { headers: Record<string, string | string[] | undefined> }): boolean {
  if (!INTERNAL_API_KEY) return true;
  const key = req.headers['x-internal-service-key'];
  const val = Array.isArray(key) ? key[0] : key;
  return val === INTERNAL_API_KEY;
}

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
app.post<{
  Body: { to?: string; subject?: string; text?: string; html?: string };
}>('/api/v1/notifications/send-email', async (req, reply) => {
  if (!checkInternalAuth(req)) {
    return reply.status(401).send({ error: 'Invalid or missing X-Internal-Service-Key' });
  }
  if (!isSmtpConfigured()) {
    return reply.status(503).send({
      error: 'SMTP not configured. Set SMTP_URL (smtp://user:pass@host:587)',
    });
  }
  const { to, subject, text, html } = req.body ?? {};
  if (!to?.trim()) {
    return reply.status(400).send({ error: 'to (email) is required' });
  }
  if (!emailRegex.test(to.trim())) {
    return reply.status(400).send({ error: 'to must be a valid email address' });
  }
  if (!subject?.trim()) {
    return reply.status(400).send({ error: 'subject is required' });
  }
  if (!text?.trim() && !html?.trim()) {
    return reply.status(400).send({ error: 'text or html is required' });
  }
  try {
    await sendEmail({
      to: to.trim(),
      subject: subject.trim(),
      text: text?.trim(),
      html: html?.trim(),
    });
    return reply.status(200).send({ ok: true });
  } catch (err) {
    req.log.error({ err, to }, 'Email send failed');
    return reply.status(500).send({
      error: 'Failed to send email',
      details: err instanceof Error ? err.message : 'Unknown error',
    });
  }
});

startReminderWorker();

try {
  await app.listen({ port: PORT, host: '0.0.0.0' });
  console.log('notifications-svc listening on :' + PORT);
} catch (err) {
  console.error(err);
  process.exit(1);
}
