import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
import jwt from 'jsonwebtoken';
import { UserRole, UserStatus } from '@prisma/client';
import { prisma } from '../../../config/database';
import { env } from '../../../config/env';
import { requireAdminRole } from '../middleware/auth';
import { logger } from '../../../utils/logger';
import { hasAnyRole, MEDIA_ROLES, getUserRoles } from '../../../utils/roles';
import { verifyMediaSignature } from '../../../utils/signed-url';
import { unlink } from 'fs/promises';

/**
 * Admin check for photo routes. Accepts Bearer header OR signed URL params.
 * Legacy `?token=<JWT>` path removed 2026-04-12 (see video-streaming.routes.ts).
 */
async function isAdminRequest(request: FastifyRequest): Promise<boolean> {
  try {
    let userId: string | undefined;
    const authHeader = request.headers.authorization;
    const query = request.query as Record<string, string | undefined>;

    if (authHeader?.startsWith('Bearer ')) {
      const payload = jwt.verify(authHeader.substring(7), env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as {
        id: string; role: UserRole; roles?: UserRole[];
      };
      if (!hasAnyRole(payload, MEDIA_ROLES)) return false;
      userId = payload.id;
    } else if (query.sig && query.exp && query.uid) {
      const result = verifyMediaSignature(request.url, query);
      if (!result.valid) return false;
      userId = result.userId;
    }

    if (!userId) return false;

    const user = await prisma.user.findUnique({
      where: { id: userId },
      select: { status: true, role: true, roles: true },
    });
    if (!user || user.status !== UserStatus.ACTIVE) return false;
    return hasAnyRole({ role: user.role as UserRole, roles: getUserRoles(user) }, MEDIA_ROLES);
  } catch {
    return false;
  }
}

/**
 * Admin photo CRUD routes (prefix: /api/photos)
 */

interface PhotosQuery {
  limit?: string;
  offset?: string;
  search?: string;
  format?: string;
  orientation?: 'H' | 'V' | 'S';
  producer?: string;
  albumId?: string;
  isPublished?: string;
}

interface PhotoUpdateBody {
  title?: string;
  description?: string;
  producer?: string;
  creator?: string;
  tags?: string[];
  category?: string;
  accessLevel?: string;
}

interface BulkIdsBody {
  ids: number[];
}

export async function photosRoutes(fastify: FastifyInstance) {
  // GET /api/photos - List photos (admin, paginated)
  fastify.get<{ Querystring: PhotosQuery }>(
    '/',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const limit = Math.min(parseInt(request.query.limit || '48'), 200);
      const offset = parseInt(request.query.offset || '0');
      const { search, format, orientation, producer, albumId, isPublished } = request.query;

      const where: any = {};

      if (search) {
        where.OR = [
          { title: { contains: search, mode: 'insensitive' } },
          { originalFilename: { contains: search, mode: 'insensitive' } },
          { producer: { contains: search, mode: 'insensitive' } },
        ];
      }
      if (format) where.format = format;
      if (orientation) where.orientation = orientation;
      if (producer) where.producer = producer;
      if (albumId) where.albumId = parseInt(albumId);
      if (isPublished !== undefined) where.isPublished = isPublished === 'true';

      const [photos, total] = await Promise.all([
        prisma.photo.findMany({
          where,
          orderBy: { createdAt: 'desc' },
          take: limit,
          skip: offset,
          include: {
            album: { select: { id: true, title: true } },
          },
        }),
        prisma.photo.count({ where }),
      ]);

      return {
        photos: photos.map(p => ({
          ...p,
          fileSize: p.fileSize?.toString() ?? null,
          thumbnailUrl: p.thumbnailPath ? `/media/photos/${p.id}/thumbnail` : null,
        })),
        total,
        limit,
        offset,
      };
    }
  );

  // GET /api/photos/producers - Distinct producers list
  fastify.get(
    '/producers',
    { preHandler: requireAdminRole },
    async () => {
      const results = await prisma.photo.findMany({
        where: { producer: { not: null } },
        select: { producer: true },
        distinct: ['producer'],
      });
      return results.map(r => r.producer).filter(Boolean);
    }
  );

  // GET /api/photos/formats - Distinct formats list
  fastify.get(
    '/formats',
    { preHandler: requireAdminRole },
    async () => {
      const results = await prisma.photo.findMany({
        where: { format: { not: null } },
        select: { format: true },
        distinct: ['format'],
      });
      return results.map(r => r.format).filter(Boolean);
    }
  );

  // GET /api/photos/:id - Single photo detail
  fastify.get<{ Params: { id: string } }>(
    '/:id',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      const photo = await prisma.photo.findUnique({
        where: { id },
        include: {
          album: { select: { id: true, title: true } },
          uploader: { select: { id: true, name: true, email: true } },
        },
      });

      if (!photo) {
        return reply.code(404).send({ message: 'Photo not found' });
      }

      return {
        ...photo,
        fileSize: photo.fileSize?.toString() ?? null,
        thumbnailUrl: photo.thumbnailPath ? `/media/photos/${photo.id}/thumbnail` : null,
      };
    }
  );

  // GET /api/photos/:id/thumbnail - Serve thumbnail image
  // Public endpoint with admin bypass for unpublished photos (matches video thumbnail pattern)
  fastify.get<{ Params: { id: string } }>(
    '/:id/thumbnail',
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      if (isNaN(id)) {
        return reply.code(400).send({ message: 'Invalid photo ID' });
      }

      // Admin bypass: skip publication filter for authenticated admin users
      const admin = await isAdminRequest(request);
      const photo = await prisma.photo.findFirst({
        where: admin
          ? { id }
          : { id, isPublished: true, isLocked: false },
        select: { thumbnailPath: true },
      });

      if (!photo?.thumbnailPath) {
        return reply.code(404).send({ message: 'Thumbnail not found' });
      }

      if (photo.thumbnailPath.includes('..')) {
        logger.warn(`Path traversal attempt detected: ${photo.thumbnailPath}`);
        return reply.code(403).send({ message: 'Access denied' });
      }

      const { createReadStream } = await import('fs');
      const { access } = await import('fs/promises');

      try {
        await access(photo.thumbnailPath);
      } catch {
        return reply.code(404).send({ message: 'Thumbnail file not found' });
      }

      reply.header('Content-Type', 'image/jpeg');
      reply.header('Cache-Control', 'public, max-age=86400');
      return reply.send(createReadStream(photo.thumbnailPath));
    }
  );

  // GET /api/photos/:id/image - Serve full image (admin, size: thumb/medium/large)
  fastify.get<{ Params: { id: string }; Querystring: { size?: string } }>(
    '/:id/image',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      const size = request.query.size || 'large';

      const photo = await prisma.photo.findUnique({
        where: { id },
        select: { thumbnailPath: true, mediumPath: true, largePath: true, webpPath: true, path: true, format: true },
      });

      if (!photo) {
        return reply.code(404).send({ message: 'Photo not found' });
      }

      // Pick variant based on size param
      let filePath: string | null = null;
      let contentType = 'image/jpeg';

      switch (size) {
        case 'thumb':
          filePath = photo.thumbnailPath;
          break;
        case 'medium':
          filePath = photo.mediumPath;
          break;
        case 'large':
        default:
          filePath = photo.largePath || photo.mediumPath;
          break;
      }

      if (!filePath || filePath.includes('..')) {
        return reply.code(404).send({ message: 'Image variant not found' });
      }

      const { createReadStream } = await import('fs');
      const { access } = await import('fs/promises');

      try {
        await access(filePath);
      } catch {
        return reply.code(404).send({ message: 'Image file not found' });
      }

      reply.header('Content-Type', contentType);
      reply.header('Cache-Control', 'public, max-age=86400');
      return reply.send(createReadStream(filePath));
    }
  );

  // PATCH /api/photos/:id - Update photo metadata
  fastify.patch<{ Params: { id: string }; Body: PhotoUpdateBody }>(
    '/:id',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      const { title, description, producer, creator, tags, category, accessLevel } = request.body;

      const photo = await prisma.photo.findUnique({ where: { id } });
      if (!photo) {
        return reply.code(404).send({ message: 'Photo not found' });
      }

      const updated = await prisma.photo.update({
        where: { id },
        data: {
          ...(title !== undefined && { title }),
          ...(description !== undefined && { description }),
          ...(producer !== undefined && { producer }),
          ...(creator !== undefined && { creator }),
          ...(tags !== undefined && { tags: tags as any }),
          ...(category !== undefined && { category }),
          ...(accessLevel !== undefined && { accessLevel }),
        },
      });

      return { ...updated, fileSize: updated.fileSize?.toString() ?? null };
    }
  );

  // DELETE /api/photos/:id - Delete photo + variant files
  fastify.delete<{ Params: { id: string } }>(
    '/:id',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);

      const photo = await prisma.photo.findUnique({ where: { id } });
      if (!photo) {
        return reply.code(404).send({ message: 'Photo not found' });
      }

      // Delete variant files
      const filesToDelete = [
        photo.path,
        photo.thumbnailPath,
        photo.mediumPath,
        photo.largePath,
        photo.webpPath,
      ].filter(Boolean) as string[];

      for (const filePath of filesToDelete) {
        try {
          await unlink(filePath);
        } catch {
          // File may already be gone
        }
      }

      // If this was a cover photo for an album, clear the cover
      if (photo.albumId) {
        await prisma.photoAlbum.updateMany({
          where: { coverPhotoId: id },
          data: { coverPhotoId: null },
        });
      }

      await prisma.photo.delete({ where: { id } });

      return { message: 'Photo deleted' };
    }
  );

  // POST /api/photos/:id/publish
  fastify.post<{ Params: { id: string } }>(
    '/:id/publish',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      const photo = await prisma.photo.update({
        where: { id },
        data: { isPublished: true, publishedAt: new Date() },
      });
      return { ...photo, fileSize: photo.fileSize?.toString() ?? null };
    }
  );

  // POST /api/photos/:id/unpublish
  fastify.post<{ Params: { id: string } }>(
    '/:id/unpublish',
    { preHandler: requireAdminRole },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      const photo = await prisma.photo.update({
        where: { id },
        data: { isPublished: false },
      });
      return { ...photo, fileSize: photo.fileSize?.toString() ?? null };
    }
  );

  // POST /api/photos/bulk-publish
  fastify.post<{ Body: BulkIdsBody }>(
    '/bulk-publish',
    { preHandler: requireAdminRole },
    async (request) => {
      const { ids } = request.body;
      const result = await prisma.photo.updateMany({
        where: { id: { in: ids } },
        data: { isPublished: true, publishedAt: new Date() },
      });
      return { updated: result.count };
    }
  );

  // POST /api/photos/bulk-unpublish
  fastify.post<{ Body: BulkIdsBody }>(
    '/bulk-unpublish',
    { preHandler: requireAdminRole },
    async (request) => {
      const { ids } = request.body;
      const result = await prisma.photo.updateMany({
        where: { id: { in: ids } },
        data: { isPublished: false },
      });
      return { updated: result.count };
    }
  );

  // POST /api/photos/bulk-delete
  fastify.post<{ Body: BulkIdsBody }>(
    '/bulk-delete',
    { preHandler: requireAdminRole },
    async (request) => {
      const { ids } = request.body;

      // Get file paths before deleting
      const photos = await prisma.photo.findMany({
        where: { id: { in: ids } },
        select: { path: true, thumbnailPath: true, mediumPath: true, largePath: true, webpPath: true },
      });

      // Delete files
      for (const photo of photos) {
        const paths = [photo.path, photo.thumbnailPath, photo.mediumPath, photo.largePath, photo.webpPath].filter(Boolean) as string[];
        for (const p of paths) {
          try { await unlink(p); } catch { /* ignore */ }
        }
      }

      // Clear album covers referencing these photos
      await prisma.photoAlbum.updateMany({
        where: { coverPhotoId: { in: ids } },
        data: { coverPhotoId: null },
      });

      const result = await prisma.photo.deleteMany({ where: { id: { in: ids } } });
      return { deleted: result.count };
    }
  );
}
