import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { LibraryEntity } from './entities/library.entity';
import { CreateLibraryDto } from './dto/create-library.dto';
import { UpdateLibraryDto } from './dto/update-library.dto';
import { QueryLibraryDto } from './dto/query-library.dto';
import { FileUploadService } from 'modules/file-upload/file-upload.service';

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

  constructor(
    @InjectRepository(LibraryEntity)
    private readonly libraryRepository: Repository<LibraryEntity>,
    private readonly fileUploadService: FileUploadService,
  ) {
    this.logger.log('LibraryService initialized');
  }

  async create(createLibraryDto: CreateLibraryDto, file: Express.Multer.File): Promise<LibraryEntity> {
    this.logger.log(`Creating new library item: ${JSON.stringify(createLibraryDto)}`);
    if (!file) {
      throw new NotFoundException('File not found');
    }
    const fileUploadUrl = this.fileUploadService.uploadImageToCloudinary(file, 'library');
    createLibraryDto.attachmentUrl = await fileUploadUrl;
    createLibraryDto.attachmentName = file.originalname;
    createLibraryDto.attachmentSize = file.size.toString();
    createLibraryDto.attachmentType = file.mimetype;

    const library = this.libraryRepository.create(createLibraryDto);
    return await this.libraryRepository.save(library);
  }

  async findAll(queryParams: QueryLibraryDto): Promise<[LibraryEntity[], number]> {
    const { limit = 10, page = 1, isPublic, isActive, category, tags, search } = queryParams;
    const skip = (page - 1) * limit;

    let query = this.libraryRepository
      .createQueryBuilder('library')
      .leftJoin('library.attachmentPostedBy', 'user')
      .select(['library', 'user.id', 'user.firstName', 'user.lastName', 'user.email', 'user.role', 'user.picture'])
      .where('1=1');

    // Apply filters
    if (isPublic !== undefined) {
      query = query.andWhere('library.isPublic = :isPublic', { isPublic });
    }

    if (isActive !== undefined) {
      query = query.andWhere('library.isActive = :isActive', { isActive });
    }

    if (category) {
      query = query.andWhere('library.attachmentCategory = :category', { category });
    }

    if (tags && tags.length > 0) {
      // Handle array of tags with overlap
      query = query.andWhere('library.tags LIKE :tags', { tags: `%${tags}%` });
    }

    if (search) {
      query = query.andWhere('(library.attachmentName LIKE :search OR library.attachmentDescription LIKE :search)', { search: `%${search}%` });
    }

    // Add pagination
    query = query.skip(skip).take(limit).orderBy('library.createdAt', 'DESC');

    return await query.getManyAndCount();
  }

  async findOne(id: number): Promise<LibraryEntity> {
    const library = await this.libraryRepository.findOne({
      where: { id },
      relations: ['attachmentPostedBy'],
    });

    if (!library) {
      this.logger.warn(`Library item with ID ${id} not found`);
      throw new NotFoundException(`Library item with ID ${id} not found`);
    }

    return library;
  }

  async getAllAttachmentUrls(
    page = 1,
    limit = 10,
  ): Promise<{
    data: string[];
    total: number;
    page: number;
    limit: number;
  }> {
    const skip = (page - 1) * limit;

    const [libraries, total] = await this.libraryRepository.findAndCount({
      select: ['attachmentUrl'],
      skip,
      take: limit,
      order: { createdAt: 'DESC' },
    });

    const data = libraries.map(library => library.attachmentUrl);

    return { data, total, page, limit };
  }

  async update(id: number, updateLibraryDto: UpdateLibraryDto): Promise<LibraryEntity> {
    const library = await this.findOne(id);

    // Update the entity with new values
    Object.assign(library, updateLibraryDto);

    // Update the updatedAt timestamp
    library.updatedAt = new Date();

    this.logger.log(`Updating library item with ID ${id}`);
    return await this.libraryRepository.save(library);
  }

  async remove(id: number): Promise<void> {
    const library = await this.findOne(id);

    this.logger.log(`Removing library item with ID ${id}`);
    await this.libraryRepository.remove(library);
  }

  async softDelete(id: number): Promise<LibraryEntity> {
    const library = await this.findOne(id);

    library.isActive = false;
    library.updatedAt = new Date();

    this.logger.log(`Soft deleting library item with ID ${id}`);
    return await this.libraryRepository.save(library);
  }

  async findByUserId(userId: number): Promise<LibraryEntity[]> {
    return await this.libraryRepository.find({
      where: {
        attachmentPostedBy: { id: userId },
        isActive: true,
      },
      relations: ['attachmentPostedBy'],
      order: { createdAt: 'DESC' },
    });
  }

  async findPublic(): Promise<LibraryEntity[]> {
    return await this.libraryRepository.find({
      where: {
        isPublic: true,
        isActive: true,
      },
      relations: ['attachmentPostedBy'],
      order: { createdAt: 'DESC' },
    });
  }

  async togglePublicStatus(id: number): Promise<LibraryEntity> {
    const library = await this.findOne(id);

    library.isPublic = !library.isPublic;
    library.updatedAt = new Date();

    this.logger.log(`Toggling public status for library item with ID ${id} to ${library.isPublic}`);
    return await this.libraryRepository.save(library);
  }

  async findByCategory(category: string): Promise<LibraryEntity[]> {
    return await this.libraryRepository.find({
      where: {
        attachmentCategory: category,
        isActive: true,
      },
      relations: ['attachmentPostedBy'],
      order: { createdAt: 'DESC' },
    });
  }

  async findByTags(tags: string[]): Promise<LibraryEntity[]> {
    // This is a simplified version, in a real app you might want more sophisticated tag matching
    const libraries = await this.libraryRepository.find({
      where: { isActive: true },
      relations: ['attachmentPostedBy'],
    });

    return libraries.filter(library => {
      if (!library.tags) return false;
      return tags.some(tag => library.tags.includes(tag));
    });
  }
}
