import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { CreateReviewDto } from '../dto/create-review.dto';
import { UpdateReviewDto } from '../dto/update-review.dto';
import { FindRestuarantReviewsDto, FindReviewsDto } from '../dto/find-reviews.dto';
import { ReviewRepository } from '../repositories/review.repository';
import { FoodRepository } from '../../foods/repositories/food.repository';
import { UserRepository } from '../../users/repositories/user.repository';
import { EntityManager } from '@mikro-orm/postgresql';
import { RequiredEntityData } from '@mikro-orm/core';
import { Review } from '../entities/review.entity';
import { FoodMessage, UserMessage, ReviewMessage } from 'src/common/enums/message.enum';
import { Order } from '../../orders/entities/order.entity';
import { OrderItem } from '../../orders/entities/order-item.entity';
import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity';
import { ReviewStatus } from '../enums/review-status.enum';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ReviewCreatedEvent } from '../events/review.events';
import { sanitizeText } from '../../utils/sanitize.util';

@Injectable()
export class ReviewService {
  constructor(
    private readonly reviewRepository: ReviewRepository,
    private readonly foodRepository: FoodRepository,
    private readonly userRepository: UserRepository,
    private readonly em: EntityManager,
    private readonly eventEmitter: EventEmitter2,
  ) { }

  async create(userId: string, createReviewDto: CreateReviewDto): Promise<Review> {
    const { foodId, orderId, rating, comment, positivePoints, negativePoints } = createReviewDto;

    const food = await this.foodRepository.findOne({ id: foodId });
    if (!food) {
      throw new NotFoundException(FoodMessage.NOT_FOUND);
    }

    const user = await this.userRepository.findOne({ id: userId });
    if (!user) {
      throw new NotFoundException(UserMessage.USER_NOT_FOUND);
    }

    // Find order and verify it belongs to the user, populate restaurant to get restaurantId
    const order = await this.em.findOne(Order, { id: orderId, user: { id: userId } }, { populate: ['restaurant'] });
    if (!order) {
      throw new NotFoundException('Order not found or does not belong to the current user');
    }

    // Verify the food is in the order
    const orderItem = await this.em.findOne(OrderItem, {
      order: { id: orderId },
      food: { id: foodId },
    });
    if (!orderItem) {
      throw new BadRequestException('Food is not in the specified order');
    }

    // Check if user already commented on this food for this order
    const existingReview = await this.reviewRepository.findOne({
      order: { id: orderId },
      food: { id: foodId },
      user: { id: userId },
    });

    if (existingReview) {
      throw new BadRequestException(ReviewMessage.ALREADY_COMMENTED);
    }

    const data: RequiredEntityData<Review> = {
      order,
      food,
      user,
      rating,
      comment: sanitizeText(comment) || undefined,
      positivePoints: positivePoints || undefined,
      negativePoints: negativePoints || undefined,
      status: ReviewStatus.PENDING,
    };

    const review = this.reviewRepository.create(data);
    if (!review) {
      throw new BadRequestException('Failed to create review entity');
    }

    await this.em.persistAndFlush(review);

    // Update food rating average
    await this.updateFoodRating(foodId);

    // Emit ReviewCreatedEvent
    this.eventEmitter.emit(
      ReviewCreatedEvent.name,
      new ReviewCreatedEvent(
        review.id,
        order.restaurant.id,
        userId,
        foodId,
        food.title || 'Unknown Food',
        orderId,
        order.orderNumber || '',
        rating,
      ),
    );

    return review;
  }

  async findAll(dto: FindReviewsDto) {
    return this.reviewRepository.findAllPaginated(dto);
  }

  async findRestuarantAllReviews(dto: FindRestuarantReviewsDto) {
    const { restuarantSlug, ...restDto } = dto;
    const restaurant = await this.em.findOne(Restaurant, { slug: restuarantSlug });
    if (!restaurant) {
      throw new NotFoundException('RestaurantMessage.NOT_FOUND');
    }
    return this.reviewRepository.findAllPaginated({ ...restDto, restId: restaurant.id });
  }

  async findById(id: string, restaurantId: string): Promise<Review> {
    const review = await this.reviewRepository.findOne(
      { id, order: { restaurant: { id: restaurantId } } },
      { populate: ['food', 'user', 'order'] },
    );
    if (!review) {
      throw new NotFoundException(ReviewMessage.NOT_FOUND);
    }
    return review;
  }

  async update(id: string, userId: string, dto: UpdateReviewDto, isAdmin: boolean = false): Promise<Review> {
    const review = await this.reviewRepository.findOne({ id }, { populate: ['food', 'user', 'order'] });
    if (!review) {
      throw new NotFoundException(ReviewMessage.NOT_FOUND);
    }

    // Only allow user to update their own reviews (unless admin)
    if (!isAdmin && review.user.id !== userId) {
      throw new BadRequestException(ReviewMessage.CAN_ONLY_UPDATE_OWN);
    }

    // Users can only update rating and comment, not status
    if (!isAdmin && dto.status !== undefined) {
      throw new BadRequestException('Only admins can change review status');
    }

    const oldRating = review.rating;

    // Sanitize comment if provided
    const sanitizedDto = {
      ...dto,
      comment: dto.comment !== undefined ? sanitizeText(dto.comment) || undefined : undefined,
    };

    this.em.assign(review, sanitizedDto);
    await this.em.persistAndFlush(review);

    // Update food rating average if rating changed
    if (dto.rating !== undefined && dto.rating !== oldRating) {
      await this.updateFoodRating(review.food.id);
    }

    return review;
  }

  async changeStatus(reviewId: string, status: ReviewStatus, restaurantId: string): Promise<Review> {
    const review = await this.reviewRepository.findOne({ id: reviewId, order: { restaurant: { id: restaurantId } } });
    if (!review) {
      throw new NotFoundException(ReviewMessage.NOT_FOUND);
    }
    review.status = status;
    await this.em.persistAndFlush(review);
    return review;
  }

  async remove(id: string, userId: string, isAdmin: boolean = false) {
    const review = await this.reviewRepository.findOne({ id }, { populate: ['food', 'user', 'order'] });
    if (!review) {
      throw new NotFoundException(ReviewMessage.NOT_FOUND);
    }

    // Only allow user to delete their own reviews (unless admin)
    if (!isAdmin && review.user.id !== userId) {
      throw new BadRequestException(ReviewMessage.CAN_ONLY_DELETE_OWN);
    }

    const foodId = review.food.id;
    review.deletedAt = new Date();
    await this.em.persistAndFlush(review);

    // Update food rating average
    await this.updateFoodRating(foodId);
  }

  private async updateFoodRating(foodId: string): Promise<void> {
    const reviews = await this.reviewRepository.find(
      { food: { id: foodId }, status: ReviewStatus.APPROVED, deletedAt: null },
      { fields: ['rating'] },
    );

    if (reviews.length === 0) {
      const food = await this.foodRepository.findOne({ id: foodId });
      if (food) {
        food.score = 0;
        await this.em.persistAndFlush(food);
      }
      return;
    }

    const averageRating = reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length;

    const food = await this.foodRepository.findOne({ id: foodId });
    if (food) {
      food.score = parseFloat(averageRating.toFixed(2));
      await this.em.persistAndFlush(food);
    }
  }
}
