import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Photo, PhotoType } from '../../database/entities/photo.entity';
import { StorageService } from '../../common/services/storage.service';
import { AuditService } from '../../common/services/audit.service';
import { EntityType } from '../../database/entities';
import { v4 as uuidv4 } from 'uuid';

export interface UploadPhotoDto {
  userId: string;
  childId?: string;
  activityId?: string;
  type: PhotoType;
  caption?: string;
  description?: string;
  takenAt?: Date;
  metadata?: Record<string, any>;
}

@Injectable()
export class PhotosService {
  private readonly logger = new Logger(PhotosService.name);

  constructor(
    @InjectRepository(Photo)
    private photoRepository: Repository<Photo>,
    private storageService: StorageService,
    private auditService: AuditService,
  ) {}

  /**
   * Upload a photo with thumbnail generation
   */
  async uploadPhoto(
    file: Express.Multer.File,
    dto: UploadPhotoDto,
  ): Promise<Photo> {
    try {
      const fileId = uuidv4();
      const extension = file.mimetype.split('/')[1] || 'jpg';
      const storageKey = `photos/${dto.userId}/${dto.childId || 'general'}/${fileId}.${extension}`;
      const thumbnailKey = `photos/${dto.userId}/${dto.childId || 'general'}/thumbnails/${fileId}_thumb.jpg`;

      // Upload original image with optimization
      const uploadResult = await this.storageService.uploadImage(
        file.buffer,
        storageKey,
        {
          maxWidth: 1920,
          maxHeight: 1920,
          quality: 85,
        },
      );

      // Generate and upload thumbnail
      const thumbnailResult = await this.storageService.generateThumbnail(
        file.buffer,
        thumbnailKey,
        300,
        300,
      );

      // Create photo record
      const photo = this.photoRepository.create({
        userId: dto.userId,
        childId: dto.childId || null,
        activityId: dto.activityId || null,
        type: dto.type,
        originalFilename: file.originalname,
        mimeType: file.mimetype,
        fileSize: uploadResult.metadata.size,
        storageKey: uploadResult.key,
        thumbnailKey: thumbnailResult.key,
        width: uploadResult.metadata.width,
        height: uploadResult.metadata.height,
        caption: dto.caption || null,
        description: dto.description || null,
        takenAt: dto.takenAt || null,
        metadata: dto.metadata || null,
      });

      const saved = await this.photoRepository.save(photo);

      // Audit log
      await this.auditService.logCreate(
        EntityType.ACTIVITY, // Using ACTIVITY for now, could add PHOTO type
        saved.id,
        {
          type: dto.type,
          childId: dto.childId,
          storageKey: uploadResult.key,
        },
        dto.userId,
      );

      this.logger.log(`Photo uploaded: ${saved.id} for user ${dto.userId}`);

      return saved;
    } catch (error) {
      this.logger.error('Failed to upload photo', error);
      throw new Error(`Photo upload failed: ${error.message}`);
    }
  }

  /**
   * Get photos for a child
   */
  async getChildPhotos(
    childId: string,
    options?: {
      type?: PhotoType;
      limit?: number;
      offset?: number;
    },
  ): Promise<{ photos: Photo[]; total: number }> {
    const query = this.photoRepository
      .createQueryBuilder('photo')
      .where('photo.childId = :childId', { childId })
      .orderBy('photo.createdAt', 'DESC');

    if (options?.type) {
      query.andWhere('photo.type = :type', { type: options.type });
    }

    const total = await query.getCount();

    if (options?.limit) {
      query.take(options.limit);
    }

    if (options?.offset) {
      query.skip(options.offset);
    }

    const photos = await query.getMany();

    return { photos, total };
  }

  /**
   * Get photos for an activity
   */
  async getActivityPhotos(activityId: string): Promise<Photo[]> {
    return this.photoRepository.find({
      where: { activityId },
      order: { createdAt: 'ASC' },
    });
  }

  /**
   * Get a single photo by ID
   */
  async getPhoto(photoId: string, userId: string): Promise<Photo> {
    const photo = await this.photoRepository.findOne({
      where: { id: photoId, userId },
      relations: ['child', 'activity'],
    });

    if (!photo) {
      throw new NotFoundException('Photo not found');
    }

    return photo;
  }

  /**
   * Get photo with presigned URL for download
   */
  async getPhotoWithUrl(photoId: string, userId: string): Promise<any> {
    const photo = await this.getPhoto(photoId, userId);

    const url = await this.storageService.getPresignedUrl(
      photo.storageKey,
      3600,
    );
    const thumbnailUrl = photo.thumbnailKey
      ? await this.storageService.getPresignedUrl(photo.thumbnailKey, 3600)
      : url;

    return {
      ...photo,
      url,
      thumbnailUrl,
    };
  }

  /**
   * Get gallery (all photos for a child with URLs)
   */
  async getGallery(
    childId: string,
    userId: string,
    options?: {
      type?: PhotoType;
      limit?: number;
      offset?: number;
    },
  ): Promise<{ photos: any[]; total: number }> {
    const { photos, total } = await this.getChildPhotos(childId, options);

    const photosWithUrls = await Promise.all(
      photos.map(async (photo) => {
        const url = await this.storageService.getPresignedUrl(
          photo.storageKey,
          3600,
        );
        const thumbnailUrl = photo.thumbnailKey
          ? await this.storageService.getPresignedUrl(photo.thumbnailKey, 3600)
          : url;

        return {
          ...photo,
          url,
          thumbnailUrl,
        };
      }),
    );

    return { photos: photosWithUrls, total };
  }

  /**
   * Update photo metadata
   */
  async updatePhoto(
    photoId: string,
    userId: string,
    updates: {
      caption?: string;
      description?: string;
      type?: PhotoType;
      metadata?: Record<string, any>;
    },
  ): Promise<Photo> {
    const photo = await this.getPhoto(photoId, userId);

    Object.assign(photo, updates);

    const updated = await this.photoRepository.save(photo);

    this.logger.log(`Photo updated: ${photoId}`);

    return updated;
  }

  /**
   * Delete a photo
   */
  async deletePhoto(photoId: string, userId: string): Promise<void> {
    const photo = await this.getPhoto(photoId, userId);

    // Delete from storage
    try {
      await this.storageService.deleteFile(photo.storageKey);
      if (photo.thumbnailKey) {
        await this.storageService.deleteFile(photo.thumbnailKey);
      }
    } catch (error) {
      this.logger.error(
        `Failed to delete files from storage: ${photoId}`,
        error,
      );
      // Continue with database deletion even if storage deletion fails
    }

    // Delete from database
    await this.photoRepository.remove(photo);

    // Audit log
    await this.auditService.logDelete(
      EntityType.ACTIVITY,
      photoId,
      { storageKey: photo.storageKey },
      userId,
    );

    this.logger.log(`Photo deleted: ${photoId}`);
  }

  /**
   * Get milestone photos
   */
  async getMilestonePhotos(childId: string): Promise<Photo[]> {
    return this.photoRepository.find({
      where: { childId, type: PhotoType.MILESTONE },
      order: { takenAt: 'ASC', createdAt: 'ASC' },
    });
  }

  /**
   * Get recent photos
   */
  async getRecentPhotos(userId: string, limit: number = 10): Promise<Photo[]> {
    return this.photoRepository.find({
      where: { userId },
      order: { createdAt: 'DESC' },
      take: limit,
      relations: ['child'],
    });
  }

  /**
   * Get photo statistics for a child
   */
  async getPhotoStats(childId: string): Promise<{
    total: number;
    byType: Record<PhotoType, number>;
    totalSize: number;
  }> {
    const photos = await this.photoRepository.find({
      where: { childId },
    });

    const byType: Record<PhotoType, number> = {
      [PhotoType.MILESTONE]: 0,
      [PhotoType.ACTIVITY]: 0,
      [PhotoType.PROFILE]: 0,
      [PhotoType.GENERAL]: 0,
    };

    let totalSize = 0;

    photos.forEach((photo) => {
      byType[photo.type]++;
      totalSize += photo.fileSize;
    });

    return {
      total: photos.length,
      byType,
      totalSize,
    };
  }
}
