import { Injectable, NotFoundException, ForbiddenException, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Ticket } from './entities/ticket.entity';
import { Comment } from './entities/comment.entity';
import { CreateTicketDto } from './dto/create-ticket.dto';
import { UpdateTicketDto } from './dto/update-ticket.dto';
import { CreateCommentDto } from './dto/create-comment.dto';
import { UserService } from 'modules/user/users.service';
import { TicketSource, TicketStatus } from 'common/enum/ticket.enum';
import { TicketResponseDto } from './dto/response-ticket.dto';
import { FileUploadService } from 'modules/file-upload/file-upload.service';
import { v4 as uuidv4 } from 'uuid';
import { UserRole } from 'common/enum/UserRole.enum';
import { UserCommentResponseDto } from './dto/comment-response.dto';

@Injectable()
export class TicketsService {
  constructor(
    @InjectRepository(Ticket)
    private ticketsRepository: Repository<Ticket>,
    @InjectRepository(Comment)
    private commentsRepository: Repository<Comment>,
    private usersService: UserService,
    private fileUploadService: FileUploadService,
  ) {}

  private generateTicketToken(): string {
    // Generate a short, readable ticket token: prefix + 8 characters
    const prefix = 'TKT';
    const randomPart = uuidv4().substring(0, 8).toUpperCase();
    return `${prefix}-${randomPart}`;
  }

  async create(createTicketDto: CreateTicketDto, file: Express.Multer.File, userRole: UserRole, userId?: number): Promise<TicketResponseDto> {
    // Verify assignee if provided
    if (createTicketDto.assigneeId) {
      await this.usersService.findUserById(createTicketDto.assigneeId);
    }

    // Upload attachment if provided
    if (file) {
      const attachmentUrl = await this.fileUploadService.uploadImageToCloudinary(file, 'ticket');
      createTicketDto.attachmentUrl = attachmentUrl;
    }
    const ticket = this.ticketsRepository.create({
      ...createTicketDto,
      authorId: userId || null,
      ticketToken: this.generateTicketToken(),
      source: createTicketDto.source || TicketSource.MANUAL,
    });

    const response = await this.ticketsRepository.save(ticket);

    const returnTicket = await this.findOne(response.id, userRole);
    return returnTicket;
  }

  async findAll(status?: TicketStatus, assigneeId?: number, authorId?: number, labels?: string[]): Promise<TicketResponseDto[]> {
    const where = {} as any;

    switch (status) {
      case TicketStatus.OPEN:
        where.status = TicketStatus.OPEN;
        break;
      case TicketStatus.IN_PROGRESS:
        where.status = TicketStatus.IN_PROGRESS;
        break;
      case TicketStatus.CLOSED:
        where.status = TicketStatus.CLOSED;
        break;
    }

    if (assigneeId) {
      where.assigneeId = assigneeId;
    }

    if (authorId) {
      where.authorId = authorId;
    }

    if (labels) {
      where.labels = labels;
    }

    const ticket = this.ticketsRepository.find({
      where,
      relations: ['comments'],
    });

    if (!ticket) {
      throw new NotFoundException('No tickets found');
    }
    return (await ticket).map(ticket => ({
      id: ticket.id,
      authorId: ticket.authorId,
      assigneeId: ticket.assigneeId,
      attachmentUrl: ticket.attachmentUrl,
      description: ticket.description,
      labels: ticket.labels,
      priority: ticket.priority,
      referenceId: ticket.referenceId,
      source: ticket.source,
      status: ticket.status,
      ticketToken: ticket.ticketToken,
      title: ticket.title,
      comments: ticket.comments.map(comment => ({
        id: comment.id,
        content: comment.content,
        authorId: comment.authorId,
        ticketId: comment.ticketId,
        createdAt: comment.createdAt,
        updatedAt: comment.updatedAt,
        attachmentUrl: comment.attachmentUrl,
        role: comment.role,
      })) as unknown as UserCommentResponseDto[],
      createdAt: ticket.createdAt,
      updatedAt: ticket.updatedAt,
    }));
  }

  async findOne(id: number, userRole: UserRole): Promise<TicketResponseDto> {
    const ticket = await this.ticketsRepository.findOne({
      where: { id },
      relations: ['comments'],
    });

    if (!ticket) {
      throw new NotFoundException(`Ticket with ID "${id}" not found`);
    }

    const ticketResponse: TicketResponseDto = {
      id: ticket.id,
      authorId: ticket.authorId,
      assigneeId: ticket.assigneeId,
      attachmentUrl: ticket.attachmentUrl,
      description: ticket.description,
      labels: ticket.labels,
      priority: ticket.priority,
      referenceId: ticket.referenceId,
      source: ticket.source,
      status: ticket.status,
      ticketToken: ticket.ticketToken,
      title: ticket.title,
      comments: ticket.comments.map(comment => ({
        id: comment.id,
        content: comment.content,
        authorId: userRole === UserRole.ADMIN || userRole === UserRole.SUPER_ADMIN ? comment.authorId : undefined,
        ticketId: comment.ticketId,
        createdAt: comment.createdAt,
        updatedAt: comment.updatedAt,
        attachmentUrl: comment.attachmentUrl,
        role: comment.role,
      })) as unknown as UserCommentResponseDto[],
      createdAt: ticket.createdAt,
      updatedAt: ticket.updatedAt,
    };
    return ticketResponse;
  }

  async findMyTickets(userId: number, userRole: UserRole): Promise<TicketResponseDto[]> {
    const tickets = await this.ticketsRepository.find({
      where: { authorId: userId },
      relations: ['comments'],
    });

    return tickets.map(ticket => ({
      id: ticket.id,
      authorId: ticket.authorId,
      assigneeId: ticket.assigneeId,
      attachmentUrl: ticket.attachmentUrl,
      description: ticket.description,
      labels: ticket.labels,
      priority: ticket.priority,
      referenceId: ticket.referenceId,
      source: ticket.source,
      status: ticket.status,
      ticketToken: ticket.ticketToken,
      title: ticket.title,
      comments: ticket.comments.map(comment => ({
        id: comment.id,
        content: comment.content,
        authorId: userRole === UserRole.ADMIN || userRole === UserRole.SUPER_ADMIN ? comment.authorId : undefined,
        ticketId: comment.ticketId,
        createdAt: comment.createdAt,
        updatedAt: comment.updatedAt,
        attachmentUrl: comment.attachmentUrl,
        role: comment.role,
      })) as unknown as UserCommentResponseDto[],
      createdAt: ticket.createdAt,
      updatedAt: ticket.updatedAt,
    }));
  }

  async findByToken(token: string, userRole: UserRole): Promise<TicketResponseDto> {
    const ticket = await this.ticketsRepository.findOne({
      where: { ticketToken: token },
      relations: ['comments'],
    });

    if (!ticket) {
      throw new NotFoundException(`Ticket with token "${token}" not found`);
    }

    return {
      id: ticket.id,
      authorId: ticket.authorId,
      assigneeId: ticket.assigneeId,
      attachmentUrl: ticket.attachmentUrl,
      description: ticket.description,
      labels: ticket.labels,
      priority: ticket.priority,
      referenceId: ticket.referenceId,
      source: ticket.source,
      status: ticket.status,
      ticketToken: ticket.ticketToken,
      title: ticket.title,
      comments: ticket.comments.map(comment => ({
        id: comment.id,
        content: comment.content,
        authorId: userRole === UserRole.ADMIN || userRole === UserRole.SUPER_ADMIN ? comment.authorId : undefined,
        ticketId: comment.ticketId,
        createdAt: comment.createdAt,
        updatedAt: comment.updatedAt,
        attachmentUrl: comment.attachmentUrl,
        role: comment.role,
      })) as unknown as UserCommentResponseDto[],
      createdAt: ticket.createdAt,
      updatedAt: ticket.updatedAt,
    };
  }

  async update(id: number, updateTicketDto: UpdateTicketDto, userRole: UserRole): Promise<TicketResponseDto> {
    const ticket = await this.findOne(id, userRole);

    // Verify assignee if provided
    if (updateTicketDto.assigneeId) {
      await this.usersService.findUserById(Number(updateTicketDto.assigneeId));
    }
    Object.assign(ticket, updateTicketDto);
    const updatedTicket = await this.ticketsRepository.save({
      assigneeId: ticket.assigneeId,
      authorId: ticket.authorId,
      attachmentUrl: ticket.attachmentUrl,
      description: ticket.description,
      labels: ticket.labels,
      priority: ticket.priority,
      referenceId: ticket.referenceId,
      source: ticket.source,
      status: ticket.status,
      ticketToken: ticket.ticketToken,
      title: ticket.title,
      id: ticket.id,
      createdAt: ticket.createdAt,
      updatedAt: ticket.updatedAt,
    });

    const returnTicket = await this.findOne(updatedTicket.id, userRole);
    return returnTicket;
  }

  async remove(id: number, userId: number): Promise<{ message: string }> {
    const ticket = await this.ticketsRepository.findOne({
      where: { id },
      relations: ['comments'],
    });

    if (!ticket) {
      throw new NotFoundException(`Ticket with ID "${id}" not found`);
    }

    // Optional: Check if user has permission to delete
    if (ticket.authorId !== userId) {
      throw new ForbiddenException('You do not have permission to delete this ticket');
    }

    await this.ticketsRepository.remove(ticket as Ticket);

    return {
      message: 'Ticket deleted successfully',
    };
  }

  async addComment(
    ticketId: number,
    createCommentDto: CreateCommentDto,
    userId: number,
    userRole: UserRole,
    file?: Express.Multer.File,
  ): Promise<TicketResponseDto> {
    const logger = new Logger('TicketService');
    logger.log('Adding comment to ticket');
    logger.log(`Ticket ID: ${ticketId}`);
    logger.log(`User ID: ${userId}`);
    logger.log(`Comment content: ${createCommentDto.content}`);
    const ticket = await this.ticketsRepository.findOne({
      where: { id: ticketId },
      relations: ['comments'],
    });

    if (!ticket) {
      throw new NotFoundException(`Ticket with ID "${ticketId}" not found`);
    }

    logger.log(`Ticket found: ${ticketId}`);
    // Upload attachment if provided
    try {
      if (file) {
        const attachmentUrl = await this.fileUploadService.uploadImageToCloudinary(file, 'ticket-comment');
        createCommentDto.attachmentUrl = attachmentUrl;
      }
      const comment = this.commentsRepository.create({
        ...createCommentDto,
        authorId: userId,
        ticketId,
      });

      logger.log(`Comment created: ${comment}`);
      await this.commentsRepository.save(comment);

      return this.findOne(ticketId, userRole);
    } catch (error) {
      logger.error(`Error adding comment: ${error.message}`, error.stack);
      throw new NotFoundException(`Error adding comment: ${error.message}`);
    }
  }

  async getComments(ticketId: number, userRole: UserRole): Promise<Comment[]> {
    const ticket = await this.findOne(ticketId, userRole);

    if (!ticket) {
      throw new NotFoundException(`Ticket with ID "${ticketId}" not found`);
    }

    return this.commentsRepository.find({
      where: { ticketId },
      select: ['id', 'content', 'ticketId', 'isSeen', 'seenAt', 'createdAt', 'updatedAt', 'attachmentUrl', 'role'],
      order: { createdAt: 'ASC' },
    });
  }

  async markCommentAsSeen(ticketToken: string, commentId: number, isSeen: boolean, seenAt: Date): Promise<Comment> {
    const ticket = await this.ticketsRepository.findOne({
      where: { ticketToken },
    });

    if (!ticket) {
      throw new NotFoundException(`Ticket with token "${ticketToken}" not found`);
    }

    const comment = await this.commentsRepository.findOne({
      where: { id: commentId },
    });

    if (!comment) {
      throw new NotFoundException(`Comment with ID "${commentId}" not found`);
    }

    // Update the comment's seen status
    comment.isSeen = isSeen;
    comment.seenAt = seenAt;

    await this.commentsRepository.save(comment);
    // Optionally, you can return the updated comment or any other relevant data

    // Return the updated comment
    return this.commentsRepository.findOne({
      where: { id: commentId },
      select: ['id', 'content', 'ticketId', 'isSeen', 'seenAt', 'createdAt', 'updatedAt', 'attachmentUrl'],
    });
  }

  async deleteComment(commentId: number, userId: number): Promise<{ message: string }> {
    const comment = await this.commentsRepository.findOne({
      where: { id: commentId },
      relations: ['ticket'],
    });

    if (!comment) {
      throw new NotFoundException(`Comment with ID "${commentId}" not found`);
    }

    // Check if user has permission to delete
    if (comment.authorId !== userId) {
      throw new ForbiddenException('You do not have permission to delete this comment');
    }

    await this.commentsRepository.remove(comment);

    return {
      message: 'Comment deleted successfully',
    };
  }
}
