import { and, desc, eq, isNull } from 'drizzle-orm';
import { Router } from 'express';
import { z } from 'zod';

import { db } from '../db/client';
import { deviceLinks, devices, events, notificationDeliveries, notifications } from '../db/schema';
import { requireAuth } from '../middleware/auth';
import { requireDeviceAuth } from '../middleware/device-auth';
import { sendRealtimeToDevice } from '../realtime/gateway';
import { writeAuditLog } from '../services/audit';
import { enqueuePushNotification } from '../services/push';

const router = Router();

const startMotionSchema = z.object({
  title: z.string().trim().min(1).max(255).optional(),
  triggeredBy: z.string().trim().min(1).max(64).default('motion'),
  videoUrl: z.string().trim().url().optional(),
});

const endMotionSchema = z.object({
  status: z.enum(['completed', 'cancelled', 'failed']).default('completed'),
  videoUrl: z.string().trim().url().optional(),
});

const listEventsSchema = z.object({
  status: z.string().trim().optional(),
  limit: z.coerce.number().int().min(1).max(100).default(25),
});

const eventParamSchema = z.object({
  eventId: z.string().uuid(),
});

router.post('/motion/start', requireDeviceAuth, async (req, res) => {
  const parsed = startMotionSchema.safeParse(req.body ?? {});

  if (!parsed.success) {
    res.status(400).json({ message: 'Invalid request body', errors: parsed.error.flatten() });
    return;
  }

  const deviceAuth = req.deviceAuth;

  if (!deviceAuth) {
    res.status(401).json({ message: 'Unauthorized' });
    return;
  }

  const cameraDevice = await db.query.devices.findFirst({
    where: and(eq(devices.id, deviceAuth.deviceId), eq(devices.userId, deviceAuth.userId)),
  });

  if (!cameraDevice) {
    res.status(404).json({ message: 'Device not found' });
    return;
  }

  if (cameraDevice.role !== 'camera') {
    res.status(403).json({ message: 'Only camera devices can start motion events' });
    return;
  }

  const now = new Date();

  const [event] = await db
    .insert(events)
    .values({
      userId: deviceAuth.userId,
      deviceId: cameraDevice.id,
      title: parsed.data.title,
      triggeredBy: parsed.data.triggeredBy,
      status: 'recording',
      startedAt: now,
      videoUrl: parsed.data.videoUrl,
      updatedAt: now,
    })
    .returning();

  if (!event) {
    res.status(500).json({ message: 'Failed to create motion event' });
    return;
  }

  const activeLinks = await db.query.deviceLinks.findMany({
    where: and(
      eq(deviceLinks.ownerUserId, deviceAuth.userId),
      eq(deviceLinks.cameraDeviceId, cameraDevice.id),
      eq(deviceLinks.status, 'active'),
    ),
  });

  for (const link of activeLinks) {
    const delivered = sendRealtimeToDevice(link.clientDeviceId, 'motion:detected', {
      eventId: event.id,
      cameraDeviceId: cameraDevice.id,
      title: event.title,
      triggeredBy: event.triggeredBy,
      startedAt: event.startedAt,
    });

    await db.insert(notifications).values({
      eventId: event.id,
      userId: deviceAuth.userId,
      channel: delivered ? 'realtime' : 'queued',
      status: delivered ? 'delivered' : 'queued',
      isRead: false,
      sentAt: now,
    });

    if (delivered) {
      await db.insert(notificationDeliveries).values({
        ownerUserId: deviceAuth.userId,
        recipientDeviceId: link.clientDeviceId,
        type: 'motion_detected',
        payload: {
          eventId: event.id,
          cameraDeviceId: cameraDevice.id,
          startedAt: event.startedAt.toISOString(),
        },
        status: 'delivered',
        attempts: 1,
        sentAt: now,
        nextAttemptAt: now,
        updatedAt: now,
      });
    } else {
      await enqueuePushNotification({
        ownerUserId: deviceAuth.userId,
        recipientDeviceId: link.clientDeviceId,
        type: 'motion_detected',
        payload: {
          eventId: event.id,
          cameraDeviceId: cameraDevice.id,
          startedAt: event.startedAt.toISOString(),
        },
      });
    }
  }

  res.status(201).json({
    message: 'Motion event started',
    event,
    notifiedClients: activeLinks.length,
  });

  await writeAuditLog({
    ownerUserId: deviceAuth.userId,
    actorDeviceId: cameraDevice.id,
    action: 'event.motion_started',
    targetType: 'event',
    targetId: event.id,
    metadata: { notifiedClients: activeLinks.length },
    ipAddress: req.ip,
  });
});

router.post('/:eventId/motion/end', requireDeviceAuth, async (req, res) => {
  const parsedParams = eventParamSchema.safeParse(req.params);

  if (!parsedParams.success) {
    res.status(400).json({ message: 'Invalid eventId', errors: parsedParams.error.flatten() });
    return;
  }

  const parsed = endMotionSchema.safeParse(req.body ?? {});

  if (!parsed.success) {
    res.status(400).json({ message: 'Invalid request body', errors: parsed.error.flatten() });
    return;
  }

  const deviceAuth = req.deviceAuth;

  if (!deviceAuth) {
    res.status(401).json({ message: 'Unauthorized' });
    return;
  }

  const event = await db.query.events.findFirst({
    where: and(
      eq(events.id, parsedParams.data.eventId),
      eq(events.userId, deviceAuth.userId),
      eq(events.deviceId, deviceAuth.deviceId),
      isNull(events.endedAt),
    ),
  });

  if (!event) {
    res.status(404).json({ message: 'Active event not found for this camera device' });
    return;
  }

  const now = new Date();

  const [updated] = await db
    .update(events)
    .set({
      endedAt: now,
      status: parsed.data.status,
      videoUrl: parsed.data.videoUrl ?? event.videoUrl,
      updatedAt: now,
    })
    .where(eq(events.id, event.id))
    .returning();

  const activeLinks = await db.query.deviceLinks.findMany({
    where: and(
      eq(deviceLinks.ownerUserId, deviceAuth.userId),
      eq(deviceLinks.cameraDeviceId, deviceAuth.deviceId),
      eq(deviceLinks.status, 'active'),
    ),
  });

  for (const link of activeLinks) {
    const delivered = sendRealtimeToDevice(link.clientDeviceId, 'motion:ended', {
      eventId: event.id,
      cameraDeviceId: deviceAuth.deviceId,
      status: parsed.data.status,
      endedAt: now,
      videoUrl: parsed.data.videoUrl ?? event.videoUrl,
    });

    if (!delivered) {
      await enqueuePushNotification({
        ownerUserId: deviceAuth.userId,
        recipientDeviceId: link.clientDeviceId,
        type: 'motion_ended',
        payload: {
          eventId: event.id,
          cameraDeviceId: deviceAuth.deviceId,
          status: parsed.data.status,
          endedAt: now.toISOString(),
        },
      });
    }
  }

  res.json({ message: 'Motion event ended', event: updated, notifiedClients: activeLinks.length });

  await writeAuditLog({
    ownerUserId: deviceAuth.userId,
    actorDeviceId: deviceAuth.deviceId,
    action: 'event.motion_ended',
    targetType: 'event',
    targetId: event.id,
    metadata: { status: parsed.data.status, notifiedClients: activeLinks.length },
    ipAddress: req.ip,
  });
});

router.get('/', requireAuth, async (req, res) => {
  const parsed = listEventsSchema.safeParse(req.query);

  if (!parsed.success) {
    res.status(400).json({ message: 'Invalid query params', errors: parsed.error.flatten() });
    return;
  }

  const authSession = req.auth;

  if (!authSession?.user?.id) {
    res.status(401).json({ message: 'Unauthorized' });
    return;
  }

  const result = await db.query.events.findMany({
    where: eq(events.userId, authSession.user.id),
    orderBy: [desc(events.startedAt)],
    limit: parsed.data.limit,
  });

  const filtered = parsed.data.status ? result.filter((event) => event.status === parsed.data.status) : result;

  res.json({ count: filtered.length, events: filtered });
});

export default router;
