import { Injectable } from "@nestjs/common"
import { InjectRepository } from "@nestjs/typeorm"
import { Repository } from "typeorm"
import { CreateRatingDto } from "../dto/create-rating.dto"
import { UpdateRatingDto } from "../dto/update-rating.dto"

import { failureResponse, successResponse } from "src/common/response/response"
import { code } from "src/common/response/response.code"
import { errorMessage, successMessage } from "src/utils/helpers"
import { messageKey } from "src/constants/message-keys"
import { RatingRepository } from "../repositories/rating.repository"
import { Rating } from "../entities/rating.entity"
import { PREDEFINED_TAGS } from "../constants/rating-tags.constants"
import { GetRatingsDto } from "../dto/get-ratings.dto"
import { TripFleetAssignment } from "src/modules/trips/entities/fleet-trip-management.entity"
import { RATING_TYPES, RatingType } from "../constants/rating-type.constants"
import { CreateNoRatingDto } from "../dto/create-no-rating.dto"

@Injectable()
export class RatingService {
  constructor(
    private readonly ratingRepository: RatingRepository,
    @InjectRepository(TripFleetAssignment)
    private readonly tripFleetAssignmentRepository: Repository<TripFleetAssignment>,
  ) {}

  async getCustomerRatingTags() {
    try {
      const tags = PREDEFINED_TAGS.CUSTOMER_TO_DRIVER
      return successResponse(code.SUCCESS, messageKey.data_retrieve, tags)
    } catch (error) {
      return failureResponse(code.ERROR, messageKey.something_went_wrong)
    }
  }

  async getDriverRatingTags() {
    try {
      const tags = PREDEFINED_TAGS.DRIVER_TO_CUSTOMER
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, {
          ":data": "Driver rating tags",
        }),
        tags,
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  async create(createRatingDto: CreateRatingDto, isCustomer: boolean) {
    try {
      const ratingType = new Rating()
      Object.assign(ratingType, createRatingDto)
      const rating = await this.ratingRepository.save(ratingType)
      return successResponse(
        code.CREATED,
        isCustomer
          ? messageKey.rating_added
          : successMessage(messageKey.data_add, { ":data": "Rating" }),
        rating,
      )
    } catch (error) {
      if (error.code === "23505") {
        return failureResponse(
          code.VALIDATION,
          errorMessage(messageKey.already_exist, { ":data": "Rating" }),
        )
      }

      return failureResponse(
        code.ERROR,
        isCustomer
          ? messageKey.something_went_wrong
          : errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  async findAll(queryParams: GetRatingsDto) {
    try {
      const { page, limit } = queryParams
      const skip = (page - 1) * limit
      const take = limit

      const result = await this.ratingRepository.getByParams({
        skip,
        take,
      })
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Ratings" }),
        result,
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  async update(id: number, updateRatingDto: UpdateRatingDto) {
    try {
      const rating = (await this.ratingRepository.getByParams({
        where: { id },
        findOne: true,
      })) as Rating
      if (!rating) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Rating" }),
        )
      }
      const updatedRating = await this.ratingRepository.save(updateRatingDto, {
        id,
      })
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_update, { ":data": "Rating" }),
        updatedRating,
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  async remove(id: number) {
    try {
      const result = await this.ratingRepository.remove({ id })
      if (result && "affected" in result && result.affected === 0) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          errorMessage(messageKey.data_not_found, { ":data": "Rating" }),
        )
      }
      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_removed, { ":data": "Rating" }),
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  // Check if user has opted out of rating for this trip
  async hasOptedOut(
    tripId: number,
    raterId: number,
    ratingType: RatingType,
  ): Promise<boolean> {
    const existing = await this.ratingRepository.getByParams({
      where: {
        trip_id: tripId,
        rater_id: raterId,
        rating_type: ratingType as RatingType,
        no_rating: true,
      },
      findOne: true,
    })
    return !!existing
  }

  // Check if user has already submitted an actual rating
  async hasAlreadyRated(
    tripId: number,
    raterId: number,
    ratingType: RatingType,
  ): Promise<boolean> {
    const existing = await this.ratingRepository.getByParams({
      where: {
        trip_id: tripId,
        rater_id: raterId,
        rating_type: ratingType as RatingType,
        no_rating: false,
      },
      findOne: true,
    })
    return !!existing
  }

  // Set no_rating flag (opt-out of rating)
  async setNoRating(createNoRatingDto: CreateNoRatingDto) {
    try {
      // Check if already exists
      const existing = await this.ratingRepository.getByParams({
        where: {
          trip_id: createNoRatingDto.trip_id,
          rater_id: createNoRatingDto.rater_id,
          rating_type: createNoRatingDto.rating_type as RatingType,
        },
        findOne: true,
      })

      if (existing) {
        return failureResponse(
          code.VALIDATION,
          "Rating entry already exists for this trip",
        )
      }

      const rating = new Rating()
      Object.assign(rating, {
        trip_id: createNoRatingDto.trip_id,
        rater_id: createNoRatingDto.rater_id,
        rated_id: createNoRatingDto.rated_id,
        rating_type: createNoRatingDto.rating_type,
        rating: 0, // No rating value
        no_rating: true,
        no_rating_at: new Date(),
      })

      const savedRating = await this.ratingRepository.save(rating)
      return successResponse(
        code.CREATED,
        "Rating opt-out recorded successfully",
        savedRating,
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  // Get driver's last trip with rating status
  async getDriverLastTripWithRatingStatus(driverId: number) {
    try {
      // Get the driver's last trip (most recent completed trip)
      const lastTripAssignment = await this.tripFleetAssignmentRepository
        .createQueryBuilder("tfa")
        .innerJoinAndSelect("tfa.trip", "trip")
        .innerJoinAndSelect("tfa.driver", "driver")
        .innerJoinAndSelect("trip.customer", "customer")
        .where("tfa.driver_id = :driverId", { driverId })
        .andWhere("trip.status = 'completed'")
        .orderBy("trip.dropoff_datetime", "DESC")
        .take(1)
        .getOne()

      if (!lastTripAssignment) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          "No completed trips found for this driver",
        )
      }

      const trip = lastTripAssignment.trip
      const driver = lastTripAssignment.driver
      const customer = lastTripAssignment.trip.customer

      // Check if driver has already rated this trip (driver_to_customer)
      const hasRated = await this.hasAlreadyRated(
        trip.id,
        driverId,
        RATING_TYPES.DRIVER_TO_CUSTOMER,
      )

      // Check if driver has opted out
      const hasOptedOut = await this.hasOptedOut(
        trip.id,
        driverId,
        RATING_TYPES.DRIVER_TO_CUSTOMER,
      )

      // If already rated or opted out, return message
      if (hasRated || hasOptedOut) {
        return successResponse(
          code.SUCCESS,
          "Rating already submitted or opted out for this trip",
          {
            trip_id: trip.id,
            has_rated: hasRated,
            has_opted_out: hasOptedOut,
            message: hasRated
              ? "You have already given a rating for this trip"
              : "You have opted out of rating for this trip",
          },
        )
      }

      // Return trip details and driver/customer info for rating
      return successResponse(
        code.SUCCESS,
        "Last trip found - rating required",
        {
          trip: {
            id: trip.id,
            pickup_datetime: trip.pickup_datetime,
            dropoff_datetime: trip.dropoff_datetime,
            pick_up_location: trip.pick_up_location,
            drop_off_location: trip.drop_off_location,
            status: trip.status,
          },
          driver: {
            id: driver.id,
            first_name: driver.first_name,
            last_name: driver.last_name,
          },
          customer: {
            id: customer.id,
            customer_name: customer.customer_name,
          },
          rating_status: {
            has_rated: false,
            has_opted_out: false,
          },
        },
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }

  // Get customer's last trip with rating status
  async getCustomerLastTripWithRatingStatus(customerId: number) {
    try {
      // Get the customer's last trip (most recent completed trip)
      const lastTrip = await this.tripFleetAssignmentRepository
        .createQueryBuilder("tfa")
        .innerJoinAndSelect("tfa.trip", "trip")
        .innerJoinAndSelect("tfa.driver", "driver")
        .innerJoinAndSelect("trip.customer", "customer")
        .where("trip.customer_id = :customerId", { customerId })
        .andWhere("trip.status = 'completed'")
        .orderBy("trip.dropoff_datetime", "DESC")
        .take(1)
        .getOne()

      if (!lastTrip) {
        return failureResponse(
          code.DATA_NOT_FOUND,
          "No completed trips found for this customer",
        )
      }

      const trip = lastTrip.trip
      const driver = lastTrip.driver
      const customer = lastTrip.trip.customer

      // Check if customer has already rated this trip (customer_to_driver)
      const hasRated = await this.hasAlreadyRated(
        trip.id,
        customerId,
        RATING_TYPES.CUSTOMER_TO_DRIVER,
      )

      // Check if customer has opted out
      const hasOptedOut = await this.hasOptedOut(
        trip.id,
        customerId,
        RATING_TYPES.CUSTOMER_TO_DRIVER,
      )

      // If already rated or opted out, return message
      if (hasRated || hasOptedOut) {
        return successResponse(
          code.SUCCESS,
          "Rating already submitted or opted out for this trip",
          {
            trip_id: trip.id,
            has_rated: hasRated,
            has_opted_out: hasOptedOut,
            message: hasRated
              ? "You have already given a rating for this trip"
              : "You have opted out of rating for this trip",
          },
        )
      }

      // Return trip details and driver/customer info for rating
      return successResponse(
        code.SUCCESS,
        "Last trip found - rating required",
        {
          trip: {
            id: trip.id,
            pickup_datetime: trip.pickup_datetime,
            dropoff_datetime: trip.dropoff_datetime,
            pick_up_location: trip.pick_up_location,
            drop_off_location: trip.drop_off_location,
            status: trip.status,
          },
          driver: {
            id: driver.id,
            first_name: driver.first_name,
            last_name: driver.last_name,
          },
          customer: {
            id: customer.id,
            customer_name: customer.customer_name,
          },
          rating_status: {
            has_rated: false,
            has_opted_out: false,
          },
        },
      )
    } catch (error) {
      return failureResponse(
        code.ERROR,
        errorMessage(messageKey.something_went_wrong),
      )
    }
  }
}
