import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Comment, Reaction, ReactionType } from '../entities';
import {
  CreateCommentDto,
  UpdateCommentDto,
  CommentResponseDto,
  FindCommentsDto,
  CreateReactionDto,
} from './comment.dto';

@Injectable()
export class CommentService {
  constructor(
    @InjectRepository(Comment)
    private commentRepository: Repository<Comment>,
    @InjectRepository(Reaction)
    private reactionRepository: Repository<Reaction>,
  ) {}

  async create(
    createCommentDto: CreateCommentDto,
    userId: string,
  ): Promise<CommentResponseDto> {
    // Validate that comment is attached to either article or live blog
    if (!createCommentDto.articleId && !createCommentDto.liveBlogId) {
      throw new BadRequestException(
        'Comment must be attached to either an article or live blog',
      );
    }

    // If parentId is provided, verify it exists
    if (createCommentDto.parentId) {
      const parent = await this.commentRepository.findOne({
        where: { id: createCommentDto.parentId },
      });
      if (!parent) {
        throw new NotFoundException('Parent comment not found');
      }
    }

    const comment = this.commentRepository.create({
      ...createCommentDto,
      userId,
    });

    const savedComment = await this.commentRepository.save(comment);
    return this.findOne(savedComment.id);
  }

  async findAll(
    findCommentsDto: FindCommentsDto,
  ): Promise<CommentResponseDto[]> {
    const {
      articleId,
      liveBlogId,
      parentId,
      page = 1,
      limit = 20,
    } = findCommentsDto;
    const skip = (page - 1) * limit;

    const query = this.commentRepository
      .createQueryBuilder('comment')
      .leftJoinAndSelect('comment.user', 'user')
      .leftJoinAndSelect('comment.replies', 'replies')
      .leftJoinAndSelect('replies.user', 'replyUser')
      .where('comment.isVisible = :isVisible', { isVisible: true });

    if (articleId) {
      query.andWhere('comment.articleId = :articleId', { articleId });
    }

    if (liveBlogId) {
      query.andWhere('comment.liveBlogId = :liveBlogId', { liveBlogId });
    }

    if (parentId) {
      query.andWhere('comment.parentId = :parentId', { parentId });
    } else {
      query.andWhere('comment.parentId IS NULL');
    }

    query.orderBy('comment.createdAt', 'DESC').skip(skip).take(limit);

    const comments = await query.getMany();
    return comments.map((comment) => new CommentResponseDto(comment));
  }

  async findOne(id: string): Promise<CommentResponseDto> {
    const comment = await this.commentRepository
      .createQueryBuilder('comment')
      .leftJoinAndSelect('comment.user', 'user')
      .leftJoinAndSelect('comment.replies', 'replies')
      .leftJoinAndSelect('replies.user', 'replyUser')
      .where('comment.id = :id', { id })
      .andWhere('comment.isVisible = :isVisible', { isVisible: true })
      .getOne();

    if (!comment) {
      throw new NotFoundException('Comment not found');
    }

    return new CommentResponseDto(comment);
  }

  async update(
    id: string,
    updateCommentDto: UpdateCommentDto,
    userId: string,
    userRole: string,
  ): Promise<CommentResponseDto> {
    const comment = await this.commentRepository.findOne({
      where: { id },
      relations: ['user'],
    });

    if (!comment) {
      throw new NotFoundException('Comment not found');
    }

    // Only allow comment owner or admin to update
    if (comment.userId !== userId && userRole !== 'admin') {
      throw new BadRequestException('You can only update your own comments');
    }

    Object.assign(comment, updateCommentDto);
    const updatedComment = await this.commentRepository.save(comment);
    return this.findOne(updatedComment.id);
  }

  async remove(id: string, userId: string, userRole: string): Promise<void> {
    const comment = await this.commentRepository.findOne({
      where: { id },
      relations: ['user'],
    });

    if (!comment) {
      throw new NotFoundException('Comment not found');
    }

    // Only allow comment owner or admin to delete
    if (comment.userId !== userId && userRole !== 'admin') {
      throw new BadRequestException('You can only delete your own comments');
    }

    await this.commentRepository.remove(comment);
  }

  async addReaction(
    createReactionDto: CreateReactionDto,
    userId: string,
  ): Promise<void> {
    // Validate that reaction is attached to either article, live blog, or comment
    const { articleId, liveBlogId, commentId, type } = createReactionDto;

    if (!articleId && !liveBlogId && !commentId) {
      throw new BadRequestException(
        'Reaction must be attached to an article, live blog, or comment',
      );
    }

    // Check if user already reacted to this item
    const whereClause: {
      userId: string;
      articleId?: string;
      liveBlogId?: string;
      commentId?: string;
    } = { userId };
    if (articleId) whereClause.articleId = articleId;
    if (liveBlogId) whereClause.liveBlogId = liveBlogId;
    if (commentId) whereClause.commentId = commentId;

    const existingReaction = await this.reactionRepository.findOne({
      where: whereClause,
    });

    if (existingReaction) {
      // If same reaction type, remove it (toggle)
      if (existingReaction.type === (type as ReactionType)) {
        await this.reactionRepository.remove(existingReaction);

        // Update comment counts if reacting to comment
        if (commentId) {
          await this.updateCommentReactionCount(commentId, type, -1);
        }
        return;
      } else {
        // Change reaction type
        existingReaction.type = type as ReactionType;
        await this.reactionRepository.save(existingReaction);

        // Update comment counts if reacting to comment
        if (commentId) {
          // Decrement old reaction count
          await this.updateCommentReactionCount(
            commentId,
            existingReaction.type === ReactionType.LIKE
              ? ReactionType.DISLIKE
              : ReactionType.LIKE,
            -1,
          );
          // Increment new reaction count
          await this.updateCommentReactionCount(commentId, type, 1);
        }
        return;
      }
    }

    // Create new reaction
    const reaction = this.reactionRepository.create({
      ...createReactionDto,
      userId,
      type: type as ReactionType,
    });

    await this.reactionRepository.save(reaction);

    // Update comment counts if reacting to comment
    if (commentId) {
      await this.updateCommentReactionCount(commentId, type, 1);
    }
  }

  private async updateCommentReactionCount(
    commentId: string,
    type: 'like' | 'dislike',
    change: number,
  ): Promise<void> {
    const comment = await this.commentRepository.findOne({
      where: { id: commentId },
    });
    if (!comment) {
      return;
    }

    if (type === 'like') {
      comment.likeCount = Math.max(0, comment.likeCount + change);
    } else {
      comment.dislikeCount = Math.max(0, comment.dislikeCount + change);
    }

    await this.commentRepository.save(comment);
  }

  async getUserReaction(
    userId: string,
    articleId?: string,
    liveBlogId?: string,
    commentId?: string,
  ): Promise<ReactionType | null> {
    const whereClause: {
      userId: string;
      articleId?: string;
      liveBlogId?: string;
      commentId?: string;
    } = { userId };
    if (articleId) whereClause.articleId = articleId;
    if (liveBlogId) whereClause.liveBlogId = liveBlogId;
    if (commentId) whereClause.commentId = commentId;

    const reaction = await this.reactionRepository.findOne({
      where: whereClause,
    });

    return reaction ? reaction.type : null;
  }

  async getReactionCounts(
    articleId?: string,
    liveBlogId?: string,
    commentId?: string,
  ): Promise<{ likes: number; dislikes: number }> {
    const query = this.reactionRepository.createQueryBuilder('reaction');

    if (articleId) {
      query.where('reaction.articleId = :articleId', { articleId });
    } else if (liveBlogId) {
      query.where('reaction.liveBlogId = :liveBlogId', { liveBlogId });
    } else if (commentId) {
      query.where('reaction.commentId = :commentId', { commentId });
    } else {
      return { likes: 0, dislikes: 0 };
    }

    const reactions = await query.getMany();

    return {
      likes: reactions.filter((r) => r.type === ReactionType.LIKE).length,
      dislikes: reactions.filter((r) => r.type === ReactionType.DISLIKE).length,
    };
  }
}
