import { FastifyInstance } from 'fastify';
import { createReadStream } from 'fs';
import { access } from 'fs/promises';
import { resolve } from 'path';
import { prisma } from '../../../config/database';
import { optionalAuth } from '../middleware/auth';
import { logger } from '../../../utils/logger';

/**
 * Public photo/album/gallery endpoints (prefix: /api)
 */

interface PublicPhotosQuery {
  limit?: string;
  offset?: string;
  sort?: 'recent' | 'popular' | 'oldest';
  category?: string;
}

interface UnifiedGalleryQuery {
  limit?: string;
  offset?: string;
  sort?: 'recent' | 'popular';
  category?: string;
  mediaType?: 'all' | 'video' | 'photo';
}

interface ImageQuery {
  size?: 'thumb' | 'medium' | 'large';
}

export async function photosPublicRoutes(fastify: FastifyInstance) {
  // GET /api/public/photos - Published photos (paginated)
  fastify.get<{ Querystring: PublicPhotosQuery }>(
    '/public/photos',
    { preHandler: optionalAuth },
    async (request) => {
      const limit = Math.min(parseInt(request.query.limit || '24'), 100);
      const offset = parseInt(request.query.offset || '0');
      const sort = request.query.sort || 'recent';
      const category = request.query.category;

      const where: any = {
        isPublished: true,
        isLocked: false,
      };
      if (category) where.category = category;

      let orderBy: any = { publishedAt: 'desc' };
      if (sort === 'oldest') orderBy = { publishedAt: 'asc' };
      if (sort === 'popular') orderBy = { viewCount: 'desc' };

      const [photos, total] = await Promise.all([
        prisma.photo.findMany({
          where,
          select: {
            id: true,
            title: true,
            width: true,
            height: true,
            orientation: true,
            format: true,
            producer: true,
            category: true,
            publishedAt: true,
            viewCount: true,
            upvoteCount: true,
            commentCount: true,
            albumId: true,
            createdAt: true,
          },
          orderBy,
          take: limit,
          skip: offset,
        }),
        prisma.photo.count({ where }),
      ]);

      return {
        photos: photos.map(p => ({
          ...p,
          thumbnailUrl: `/media/public/photos/${p.id}/thumbnail`,
          imageUrl: `/media/public/photos/${p.id}/image`,
        })),
        pagination: { total, limit, offset, hasMore: offset + limit < total },
      };
    }
  );

  // GET /api/public/photos/:id - Single published photo
  fastify.get<{ Params: { id: string } }>(
    '/public/photos/:id',
    { preHandler: optionalAuth },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);

      const photo = await prisma.photo.findFirst({
        where: { id, isPublished: true, isLocked: false },
        select: {
          id: true,
          title: true,
          description: true,
          width: true,
          height: true,
          orientation: true,
          format: true,
          producer: true,
          creator: true,
          category: true,
          cameraMake: true,
          cameraModel: true,
          focalLength: true,
          aperture: true,
          shutterSpeed: true,
          iso: true,
          takenAt: true,
          publishedAt: true,
          viewCount: true,
          upvoteCount: true,
          commentCount: true,
          albumId: true,
          createdAt: true,
        },
      });

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

      return {
        ...photo,
        thumbnailUrl: `/media/public/photos/${photo.id}/thumbnail`,
        imageUrl: `/media/public/photos/${photo.id}/image`,
      };
    }
  );

  // GET /api/public/photos/:id/image - Serve optimized image
  fastify.get<{ Params: { id: string }; Querystring: ImageQuery }>(
    '/public/photos/:id/image',
    async (request, reply) => {
      const id = parseInt(request.params.id as string);
      const size = request.query.size || 'large';

      const photo = await prisma.photo.findFirst({
        where: { id, isPublished: true, isLocked: false },
        select: {
          thumbnailPath: true,
          mediumPath: true,
          largePath: true,
          webpPath: true,
          format: true,
        },
      });

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

      // Check if client accepts WebP
      const acceptsWebP = request.headers.accept?.includes('image/webp');

      // Pick the right variant
      let filePath: string | null = null;
      let contentType = 'image/jpeg';

      if (acceptsWebP && photo.webpPath) {
        filePath = photo.webpPath;
        contentType = 'image/webp';
      } else {
        switch (size) {
          case 'thumb':
            filePath = photo.thumbnailPath;
            break;
          case 'medium':
            filePath = photo.mediumPath;
            break;
          case 'large':
          default:
            filePath = photo.largePath;
            break;
        }
      }

      if (!filePath) {
        return reply.code(404).send({ message: 'Image variant not found' });
      }
      const PHOTOS_BASE = '/media/local/photos';
      const resolvedPath = resolve(filePath);
      if (!resolvedPath.startsWith(resolve(PHOTOS_BASE) + '/')) {
        logger.warn(`Photo path traversal attempt blocked: ${filePath}`);
        return reply.code(403).send({ message: 'Access denied' });
      }

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

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

  // GET /api/public/photos/:id/thumbnail - Serve thumbnail
  fastify.get<{ Params: { id: string } }>(
    '/public/photos/:id/thumbnail',
    async (request, reply) => {
      const id = parseInt(request.params.id as string);

      const photo = await prisma.photo.findFirst({
        where: { id, isPublished: true, isLocked: false },
        select: { thumbnailPath: true },
      });

      if (!photo?.thumbnailPath) {
        return reply.code(404).send({ message: 'Thumbnail not found' });
      }
      const PHOTOS_BASE = '/media/local/photos';
      const resolvedThumb = resolve(photo.thumbnailPath);
      if (!resolvedThumb.startsWith(resolve(PHOTOS_BASE) + '/')) {
        logger.warn(`Thumbnail path traversal attempt blocked: ${photo.thumbnailPath}`);
        return reply.code(403).send({ message: 'Access denied' });
      }

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

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

  // GET /api/public/albums - Published albums
  fastify.get<{ Querystring: PublicPhotosQuery }>(
    '/public/albums',
    { preHandler: optionalAuth },
    async (request) => {
      const limit = Math.min(parseInt(request.query.limit || '24'), 100);
      const offset = parseInt(request.query.offset || '0');
      const category = request.query.category;

      const where: any = { isPublished: true, isLocked: false };
      if (category) where.category = category;

      const [albums, total] = await Promise.all([
        prisma.photoAlbum.findMany({
          where,
          select: {
            id: true,
            title: true,
            description: true,
            category: true,
            photoCount: true,
            viewCount: true,
            upvoteCount: true,
            publishedAt: true,
            coverPhoto: {
              select: { id: true, thumbnailPath: true, width: true, height: true },
            },
          },
          orderBy: { publishedAt: 'desc' },
          take: limit,
          skip: offset,
        }),
        prisma.photoAlbum.count({ where }),
      ]);

      return {
        albums: albums.map(a => ({
          ...a,
          coverThumbnailUrl: a.coverPhoto
            ? `/media/public/photos/${a.coverPhoto.id}/thumbnail`
            : null,
        })),
        pagination: { total, limit, offset, hasMore: offset + limit < total },
      };
    }
  );

  // GET /api/public/albums/:id - Published album with photos
  fastify.get<{ Params: { id: string } }>(
    '/public/albums/:id',
    { preHandler: optionalAuth },
    async (request, reply) => {
      const id = parseInt(request.params.id as string);

      const album = await prisma.photoAlbum.findFirst({
        where: { id, isPublished: true, isLocked: false },
        include: {
          coverPhoto: { select: { id: true, thumbnailPath: true } },
          photos: {
            where: { isPublished: true, isLocked: false },
            orderBy: { albumPosition: 'asc' },
            select: {
              id: true,
              title: true,
              width: true,
              height: true,
              orientation: true,
              format: true,
              viewCount: true,
              upvoteCount: true,
              commentCount: true,
            },
          },
        },
      });

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

      return {
        ...album,
        photos: album.photos.map(p => ({
          ...p,
          thumbnailUrl: `/media/public/photos/${p.id}/thumbnail`,
          imageUrl: `/media/public/photos/${p.id}/image`,
        })),
      };
    }
  );

  // GET /api/public/gallery - Unified feed (videos + photos + albums)
  fastify.get<{ Querystring: UnifiedGalleryQuery }>(
    '/public/gallery',
    { preHandler: optionalAuth },
    async (request) => {
      const limit = Math.min(parseInt(request.query.limit || '24'), 100);
      const offset = parseInt(request.query.offset || '0');
      const sort = request.query.sort || 'recent';
      const category = request.query.category;
      const mediaType = request.query.mediaType || 'all';

      const orderByDate = sort === 'popular' ? undefined : 'desc';
      const items: Array<{ type: 'video' | 'photo' | 'album'; data: any; publishedAt: Date }> = [];

      // Fetch videos if needed
      if (mediaType === 'all' || mediaType === 'video') {
        const videoWhere: any = { isPublished: true, isLocked: false };
        if (category) videoWhere.category = category;

        const videos = await prisma.video.findMany({
          where: videoWhere,
          select: {
            id: true,
            title: true,
            filename: true,
            durationSeconds: true,
            width: true,
            height: true,
            orientation: true,
            quality: true,
            producer: true,
            thumbnailPath: true,
            publishedAt: true,
            category: true,
            viewCount: true,
            upvoteCount: true,
            commentCount: true,
            isShort: true,
            createdAt: true,
          },
          orderBy: sort === 'popular' ? { viewCount: 'desc' } : { publishedAt: 'desc' },
          take: limit + offset, // Over-fetch for merge
        });

        for (const v of videos) {
          items.push({
            type: 'video',
            data: {
              ...v,
              duration: v.durationSeconds,
              thumbnailUrl: v.thumbnailPath ? `/media/videos/${v.id}/thumbnail` : null,
              videoUrl: `/media/videos/${v.id}/stream`,
            },
            publishedAt: v.publishedAt || v.createdAt,
          });
        }
      }

      // Fetch photos (non-album) if needed
      if (mediaType === 'all' || mediaType === 'photo') {
        const photoWhere: any = { isPublished: true, isLocked: false, albumId: null };
        if (category) photoWhere.category = category;

        const photos = await prisma.photo.findMany({
          where: photoWhere,
          select: {
            id: true,
            title: true,
            width: true,
            height: true,
            orientation: true,
            format: true,
            producer: true,
            category: true,
            publishedAt: true,
            viewCount: true,
            upvoteCount: true,
            commentCount: true,
            createdAt: true,
          },
          orderBy: sort === 'popular' ? { viewCount: 'desc' } : { publishedAt: 'desc' },
          take: limit + offset,
        });

        for (const p of photos) {
          items.push({
            type: 'photo',
            data: {
              ...p,
              thumbnailUrl: `/media/public/photos/${p.id}/thumbnail`,
              imageUrl: `/media/public/photos/${p.id}/image`,
            },
            publishedAt: p.publishedAt || p.createdAt,
          });
        }

        // Fetch albums
        const albumWhere: any = { isPublished: true, isLocked: false };
        if (category) albumWhere.category = category;

        const albums = await prisma.photoAlbum.findMany({
          where: albumWhere,
          select: {
            id: true,
            title: true,
            description: true,
            category: true,
            photoCount: true,
            viewCount: true,
            upvoteCount: true,
            publishedAt: true,
            createdAt: true,
            coverPhoto: {
              select: { id: true, thumbnailPath: true, width: true, height: true },
            },
          },
          orderBy: sort === 'popular' ? { viewCount: 'desc' } : { publishedAt: 'desc' },
          take: limit + offset,
        });

        for (const a of albums) {
          items.push({
            type: 'album',
            data: {
              ...a,
              coverThumbnailUrl: a.coverPhoto
                ? `/media/public/photos/${a.coverPhoto.id}/thumbnail`
                : null,
            },
            publishedAt: a.publishedAt || a.createdAt,
          });
        }
      }

      // Sort merged results
      if (sort === 'popular') {
        items.sort((a, b) => (b.data.viewCount || 0) - (a.data.viewCount || 0));
      } else {
        items.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
      }

      // Apply offset and limit
      const paged = items.slice(offset, offset + limit);

      return {
        items: paged.map(({ type, data }) => ({ type, data })),
        pagination: {
          total: items.length,
          limit,
          offset,
          hasMore: offset + limit < items.length,
        },
      };
    }
  );
}
