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

import { db } from '../db/client';
import { devices, events, recordings } from '../db/schema';
import { requireAuth } from '../middleware/auth';
import {
  ensureMinioBucket,
  minioBucket,
  minioClient,
  minioPresignedExpirySeconds,
} from '../utils/minio';

const router = Router();

const uploadUrlSchema = z.object({
  fileName: z.string().trim().min(1).max(255),
  deviceId: z.string().uuid(),
  prefix: z.string().trim().optional(),
  recordingId: z.string().uuid().optional(),
  eventId: z.string().uuid().optional(),
});

const downloadUrlSchema = z.object({
  objectKey: z.string().trim().min(1),
});

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

const sanitizeSegment = (value: string): string => value.replace(/[^a-zA-Z0-9._/-]/g, '_');

const buildObjectKey = (userId: string, fileName: string, prefix?: string): string => {
  const safePrefix = prefix ? sanitizeSegment(prefix).replace(/^\/+|\/+$/g, '') : 'uploads';
  const safeFileName = sanitizeSegment(fileName);

  return `${safePrefix}/${userId}/${Date.now()}-${safeFileName}`;
};

router.use(requireAuth);

router.post('/upload-url', async (req, res) => {
  const parsed = uploadUrlSchema.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) {
    res.status(401).json({ message: 'Unauthorized' });
    return;
  }

  await ensureMinioBucket();

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

  if (!device) {
    res.status(400).json({ message: 'Invalid deviceId for this user' });
    return;
  }

  if (parsed.data.eventId) {
    const event = await db.query.events.findFirst({
      where: and(eq(events.id, parsed.data.eventId), eq(events.userId, authSession.user.id)),
      columns: { id: true },
    });

    if (!event) {
      res.status(400).json({ message: 'Invalid eventId for this user' });
      return;
    }
  }

  const objectKey = buildObjectKey(authSession.user.id, parsed.data.fileName, parsed.data.prefix);
  const uploadUrl = await minioClient.presignedPutObject(minioBucket, objectKey, minioPresignedExpirySeconds);
  const now = new Date();
  const expiresAt = new Date(now.getTime() + minioPresignedExpirySeconds * 1000);

  let persistedRecording;

  if (parsed.data.recordingId) {
    const existingRecording = await db.query.recordings.findFirst({
      where: and(eq(recordings.id, parsed.data.recordingId), eq(recordings.ownerUserId, authSession.user.id)),
    });

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

    [persistedRecording] = await db
      .update(recordings)
      .set({
        objectKey,
        bucket: minioBucket,
        status: 'awaiting_upload',
        updatedAt: now,
        error: null,
      })
      .where(eq(recordings.id, existingRecording.id))
      .returning();
  } else {
    [persistedRecording] = await db
      .insert(recordings)
      .values({
        ownerUserId: authSession.user.id,
        cameraDeviceId: parsed.data.deviceId,
        requesterDeviceId: null,
        eventId: parsed.data.eventId ?? null,
        objectKey,
        bucket: minioBucket,
        status: 'awaiting_upload',
        updatedAt: now,
      })
      .returning();
  }

  if (!persistedRecording) {
    res.status(500).json({ message: 'Unable to persist recording metadata' });
    return;
  }

  res.status(201).json({
    message: 'Upload URL generated',
    bucket: minioBucket,
    objectKey,
    uploadUrl,
    expiresInSeconds: minioPresignedExpirySeconds,
    video: {
      id: persistedRecording.id,
      objectKey: persistedRecording.objectKey,
      bucket: persistedRecording.bucket,
      status: persistedRecording.status,
      createdAt: persistedRecording.createdAt,
      expiresAt,
    },
  });
});

router.get('/download-url', async (req, res) => {
  const parsed = downloadUrlSchema.safeParse(req.query);

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

  await ensureMinioBucket();

  const downloadUrl = await minioClient.presignedGetObject(
    minioBucket,
    parsed.data.objectKey,
    minioPresignedExpirySeconds,
  );

  res.json({
    message: 'Dummy download URL generated',
    bucket: minioBucket,
    objectKey: parsed.data.objectKey,
    downloadUrl,
    expiresInSeconds: minioPresignedExpirySeconds,
  });
});

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

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

  await ensureMinioBucket();

  const objects = await new Promise<
    { objectKey: string | undefined; size: number; etag: string | undefined; lastModified: Date | undefined }[]
  >((resolve, reject) => {
    const results: {
      objectKey: string | undefined;
      size: number;
      etag: string | undefined;
      lastModified: Date | undefined;
    }[] = [];
    const stream = minioClient.listObjectsV2(minioBucket, parsed.data.prefix, true);

    stream.on('data', (item) => {
      if (results.length >= parsed.data.limit) {
        stream.destroy();
        return;
      }

      results.push({
        objectKey: item.name,
        size: item.size,
        etag: item.etag,
        lastModified: item.lastModified,
      });
    });

    stream.on('error', (error) => reject(error));
    stream.on('end', () => resolve(results));
    stream.on('close', () => resolve(results));
  });

  res.json({
    bucket: minioBucket,
    count: objects.length,
    objects,
  });
});

router.delete('/', async (req, res) => {
  const parsed = downloadUrlSchema.safeParse(req.query);

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

  await ensureMinioBucket();
  await minioClient.removeObject(minioBucket, parsed.data.objectKey);

  res.json({ message: 'Object deleted', bucket: minioBucket, objectKey: parsed.data.objectKey });
});

export default router;
