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

import { db } from '../db/client';
import { deviceLinks, devices } from '../db/schema';
import { requireAuth } from '../middleware/auth';
import { requireDeviceAuth } from '../middleware/device-auth';
import { createDeviceToken } from '../utils/device-token';
import { getEffectiveDeviceStatus } from '../utils/device-status';
import { getDeviceOnlineStaleSeconds } from '../utils/env';

const router = Router();

const roleSchema = z.enum(['camera', 'client']);

const registerSchema = z.object({
  name: z.string().trim().min(1).max(255).optional(),
  role: roleSchema,
  platform: z.string().trim().min(1).max(32).optional(),
  appVersion: z.string().trim().min(1).max(64).optional(),
  pushToken: z.string().trim().min(1).optional(),
});

const updateSchema = z.object({
  name: z.string().trim().min(1).max(255).optional(),
  role: roleSchema.optional(),
  platform: z.string().trim().min(1).max(32).optional(),
  appVersion: z.string().trim().min(1).max(64).optional(),
  pushToken: z.string().trim().min(1).optional(),
  status: z.enum(['online', 'offline']).optional(),
});

const heartbeatSchema = z.object({
  status: z.enum(['online', 'offline']).default('online'),
});
const deviceParamSchema = z.object({
  deviceId: z.string().uuid(),
});

router.post('/register', requireAuth, async (req, res) => {
  const parsed = registerSchema.safeParse(req.body);

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

  const authSession = req.auth;

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

  const now = new Date();
  const [device] = await db
    .insert(devices)
    .values({
      userId: authSession.user.id,
      name: parsed.data.name,
      role: parsed.data.role,
      isCamera: parsed.data.role === 'camera',
      platform: parsed.data.platform,
      appVersion: parsed.data.appVersion,
      pushToken: parsed.data.pushToken,
      status: 'online',
      lastSeenAt: now,
      updatedAt: now,
    })
    .returning({
      id: devices.id,
      userId: devices.userId,
      name: devices.name,
      role: devices.role,
      status: devices.status,
      platform: devices.platform,
      appVersion: devices.appVersion,
      lastSeenAt: devices.lastSeenAt,
      createdAt: devices.createdAt,
      updatedAt: devices.updatedAt,
    });

  if (!device) {
    res.status(500).json({ message: 'Unable to register device' });
    return;
  }

  const oppositeRole = device.role === 'camera' ? 'client' : 'camera';
  const oppositeDevices = await db.query.devices.findMany({
    where: and(eq(devices.userId, device.userId), eq(devices.role, oppositeRole)),
  });

  if (oppositeDevices.length > 0) {
    const linksToCreate = oppositeDevices.map((otherDevice) => ({
      ownerUserId: device.userId,
      cameraDeviceId: device.role === 'camera' ? device.id : otherDevice.id,
      clientDeviceId: device.role === 'client' ? device.id : otherDevice.id,
      status: 'active' as const,
    }));

    await db.insert(deviceLinks).values(linksToCreate).onConflictDoNothing();
  }

  const deviceToken = createDeviceToken({
    userId: device.userId,
    deviceId: device.id,
    role: device.role as 'camera' | 'client',
  });

  res.status(201).json({
    message: 'Device registered',
    device,
    deviceToken,
  });
});

router.get('/', requireAuth, async (req, res) => {
  const authSession = req.auth;

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

  const result = await db.query.devices.findMany({
    where: eq(devices.userId, authSession.user.id),
    orderBy: [desc(devices.updatedAt)],
  });

  const now = new Date();
  const staleAfterSeconds = getDeviceOnlineStaleSeconds();
  const effectiveDevices = result.map((device) => ({
    ...device,
    status: getEffectiveDeviceStatus({
      status: device.status,
      lastSeenAt: device.lastSeenAt,
      now,
      staleAfterSeconds,
    }),
  }));

  res.json({ count: effectiveDevices.length, devices: effectiveDevices });
});

router.patch('/:deviceId', requireAuth, async (req, res) => {
  const parsedParams = deviceParamSchema.safeParse(req.params);
  if (!parsedParams.success) {
    res.status(400).json({ message: 'Invalid device id', errors: parsedParams.error.flatten() });
    return;
  }

  const parsed = updateSchema.safeParse(req.body);

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

  const authSession = req.auth;

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

  const existing = await db.query.devices.findFirst({
    where: and(eq(devices.id, parsedParams.data.deviceId), eq(devices.userId, authSession.user.id)),
  });

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

  const role = parsed.data.role ?? existing.role;
  const now = new Date();

  const [updated] = await db
    .update(devices)
    .set({
      name: parsed.data.name ?? existing.name,
      role,
      isCamera: role === 'camera',
      platform: parsed.data.platform ?? existing.platform,
      appVersion: parsed.data.appVersion ?? existing.appVersion,
      pushToken: parsed.data.pushToken ?? existing.pushToken,
      status: parsed.data.status ?? existing.status,
      updatedAt: now,
      lastSeenAt: parsed.data.status === 'online' ? now : existing.lastSeenAt,
    })
    .where(eq(devices.id, existing.id))
    .returning();

  res.json({ message: 'Device updated', device: updated });
});

router.post('/:deviceId/heartbeat', requireDeviceAuth, async (req, res) => {
  const parsedParams = deviceParamSchema.safeParse(req.params);
  if (!parsedParams.success) {
    res.status(400).json({ message: 'Invalid device id', errors: parsedParams.error.flatten() });
    return;
  }

  const parsed = heartbeatSchema.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;
  }

  if (deviceAuth.deviceId !== parsedParams.data.deviceId) {
    res.status(403).json({ message: 'Device token does not match target device' });
    return;
  }

  const now = new Date();

  const [updated] = await db
    .update(devices)
    .set({
      status: parsed.data.status,
      lastSeenAt: now,
      updatedAt: now,
    })
    .where(and(eq(devices.id, parsedParams.data.deviceId), eq(devices.userId, deviceAuth.userId)))
    .returning({
      id: devices.id,
      status: devices.status,
      lastSeenAt: devices.lastSeenAt,
      updatedAt: devices.updatedAt,
    });

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

  res.json({ message: 'Heartbeat recorded', device: updated });
});

export default router;
