package services

import (
	"astrology-api/constants"
	dto "astrology-api/dto"
	models "astrology-api/models/usermodel"
	repositories "astrology-api/repositories"
	"errors"
	"strings"
)

type UserReviewService interface {
	AddReview(userID uint, req dto.AddReviewRequest) (*models.UserReview, error)

	UpdateReview(userID uint, reviewID uint, req dto.UpdateReviewRequest) (*models.UserReview, error)

	DeleteReview(userID uint, reviewID uint) error

	// New Methods
	GetAstrologerReviews(req dto.ReviewListRequest) ([]dto.UserReviewResponse, int64, error)

	GetUserReviewHistory(userID uint) ([]dto.UserReviewResponse, error)

	ReplyReview(astrologerID uint, req dto.ReplyReviewRequest) error

	BlockReview(userID uint, req dto.BlockReviewRequest) error

	GetBlockedReviews(userID uint) ([]dto.UserReviewResponse, error)

	GetAverageRating(astrologerID uint) (float64, error)

	GetTotalReviewCount(astrologerID uint) (int64, error)

	GetAdminReviewList(astrologerID uint) ([]dto.UserReviewResponse, error)
}

type userReviewService struct {
	userReviewRepository repositories.UserReviewRepository
	astrologerRepository repositories.AstrologerRepository

	// Only to check that a review claiming to be about a session really is
	// about one of this customer's finished sessions.
	consultationRepository repositories.ConsultationRepository
}

func NewUserReviewService(
	userReviewRepository repositories.UserReviewRepository,
	astrologerRepository repositories.AstrologerRepository,
	consultationRepository repositories.ConsultationRepository,
) UserReviewService {

	return &userReviewService{
		userReviewRepository:   userReviewRepository,
		astrologerRepository:   astrologerRepository,
		consultationRepository: consultationRepository,
	}
}

func (s *userReviewService) AddReview(userID uint, req dto.AddReviewRequest) (*models.UserReview, error) {

	if req.Rating < 1 || req.Rating > 5 {
		return nil, errors.New("rating must be between 1 and 5")
	}

	if req.Review == "" {
		return nil, errors.New("review is required")
	}

	if req.AstrologerID == 0 {
		return nil, errors.New("astrologerId is required")
	}

	// Verify astrologer exists
	astrologer, err := s.astrologerRepository.GetAstrologerByID(dto.GetAstrologerByIDRequest{AstrologerID: req.AstrologerID})
	if err != nil {
		return nil, err
	}

	if astrologer == nil {
		return nil, errors.New("astrologer not found")
	}

	review := &models.UserReview{
		UserID:             userID,
		AstrologerID:       &req.AstrologerID,
		AstromallProductID: req.AstromallProductID,
		Rating:             req.Rating,
		Review:             req.Review,
		IsPublic:           req.IsPublic,
		IsActive:           true,
		IsDelete:           false,
		CreatedBy:          userID,
		ModifiedBy:         userID,
	}

	//------------------------------------------------
	// Tie It To Its Session
	//------------------------------------------------
	//
	// Only when the consultation really is this customer's and really has
	// finished. Otherwise a review could be attached to somebody else's
	// session, or to one still running — and this link is what the admin
	// reads when deciding whether to pay the astrologer for it.

	if req.ConsultationID != nil && *req.ConsultationID > 0 {

		consultation, err := s.consultationRepository.GetConsultationByID(*req.ConsultationID)

		if err != nil {
			return nil, err
		}

		if consultation == nil ||
			consultation.UserID != userID ||
			consultation.AstrologerID != req.AstrologerID {

			return nil, errors.New("consultation not found")
		}

		if consultation.Status != constants.ConsultationStatusCompleted {
			return nil, errors.New("this consultation has not finished yet")
		}

		review.ConsultationID = req.ConsultationID
	}

	if err := s.userReviewRepository.AddReview(review); err != nil {
		return nil, err
	}

	return review, nil
}

func (s *userReviewService) UpdateReview(userID uint, reviewID uint, req dto.UpdateReviewRequest) (*models.UserReview, error) {

	review, err := s.userReviewRepository.GetReviewByID(reviewID)
	if err != nil {
		return nil, err
	}

	if review == nil {
		return nil, errors.New("review not found")
	}

	if review.UserID != userID {
		return nil, errors.New("you can update only your own review")
	}

	review.Rating = req.Rating
	review.Review = req.Review
	review.IsPublic = req.IsPublic
	review.ModifiedBy = userID

	if err := s.userReviewRepository.UpdateReview(review); err != nil {
		return nil, err
	}

	return review, nil
}

func (s *userReviewService) DeleteReview(userID uint, reviewID uint) error {

	review, err := s.userReviewRepository.GetReviewByID(reviewID)
	if err != nil {
		return err
	}

	if review == nil {
		return errors.New("review not found")
	}

	if review.UserID != userID {
		return errors.New("you can delete only your own review")
	}

	return s.userReviewRepository.DeleteReview(reviewID)
}

func (s *userReviewService) GetAstrologerReviews(req dto.ReviewListRequest) ([]dto.UserReviewResponse, int64, error) {

	if req.AstrologerID == 0 {
		return nil, 0, errors.New("astrologerId is required")
	}
	// Verify astrologer exists
	astrologer, err := s.astrologerRepository.GetAstrologerByID(dto.GetAstrologerByIDRequest{AstrologerID: req.AstrologerID})
	if err != nil {
		return nil, 0, err
	}
	if astrologer == nil {
		return nil, 0, errors.New("astrologer not found")
	}
	reviews, total, err := s.userReviewRepository.GetAstrologerReviews(req.AstrologerID, req.StartIndex, req.FetchRecord)
	if err != nil {
		return nil, 0, err
	}
	return reviews, total, nil
}

func (s *userReviewService) GetUserReviewHistory(userID uint) ([]dto.UserReviewResponse, error) {

	reviews, err := s.userReviewRepository.GetUserReviewHistory(userID)
	if err != nil {
		return nil, err
	}
	return reviews, nil

}

func (s *userReviewService) ReplyReview(astrologerID uint, req dto.ReplyReviewRequest) error {

	// Validate request
	if req.ReviewID == 0 {
		return errors.New("reviewId is required")
	}

	if strings.TrimSpace(req.Reply) == "" {
		return errors.New("reply is required")
	}

	// Check review exists
	review, err := s.userReviewRepository.GetReviewByID(req.ReviewID)
	if err != nil {
		return err
	}

	if review == nil {
		return errors.New("review not found")
	}

	// Review must belong to logged in astrologer
	if review.AstrologerID == nil {
		return errors.New("invalid review")
	}

	if *review.AstrologerID != astrologerID {
		return errors.New("you can reply only to your own reviews")
	}

	// Already replied?
	if review.Reply != nil && strings.TrimSpace(*review.Reply) != "" {
		return errors.New("reply already added")
	}
	return s.userReviewRepository.ReplyReview(req.ReviewID, req.Reply)

}

func (s *userReviewService) BlockReview(
	userID uint,
	req dto.BlockReviewRequest,
) error {

	if req.UserReviewID == 0 {
		return errors.New("userReviewId is required")
	}

	// Check review exists
	review, err := s.userReviewRepository.GetReviewByID(req.UserReviewID)
	if err != nil {
		return err
	}

	if review == nil {
		return errors.New("review not found")
	}

	// Already blocked?
	blocked, err := s.userReviewRepository.IsReviewBlocked(
		req.UserReviewID,
		userID,
	)

	if err != nil {
		return err
	}

	if blocked {
		return errors.New("review already blocked")
	}

	block := &models.ReviewBlock{
		UserReviewID: req.UserReviewID,
		UserID:       userID,
		IsActive:     true,
		IsDelete:     false,
		CreatedBy:    userID,
		ModifiedBy:   userID,
	}

	return s.userReviewRepository.BlockReview(block)
}

func (s *userReviewService) GetBlockedReviews(
	userID uint,
) ([]dto.UserReviewResponse, error) {

	reviews, err := s.userReviewRepository.GetBlockedReviews(userID)

	if err != nil {
		return nil, err
	}

	return reviews, nil
}

func (s *userReviewService) GetAverageRating(
	astrologerID uint,
) (float64, error) {

	if astrologerID == 0 {
		return 0, errors.New("astrologerId is required")
	}

	_, err := s.astrologerRepository.GetAstrologerByID(dto.GetAstrologerByIDRequest{AstrologerID: astrologerID})
	if err != nil {
		return 0, err
	}

	return s.userReviewRepository.GetAverageRating(astrologerID)
}

func (s *userReviewService) GetTotalReviewCount(
	astrologerID uint,
) (int64, error) {

	if astrologerID == 0 {
		return 0, errors.New("astrologerId is required")
	}

	_, err := s.astrologerRepository.GetAstrologerByID(dto.GetAstrologerByIDRequest{AstrologerID: astrologerID})
	if err != nil {
		return 0, err
	}

	return s.userReviewRepository.GetTotalReviewCount(astrologerID)
}

func (s *userReviewService) GetAdminReviewList(
	astrologerID uint,
) ([]dto.UserReviewResponse, error) {

	reviews, err := s.userReviewRepository.GetAdminReviewList(astrologerID)

	if err != nil {
		return nil, err
	}

	return reviews, nil
}
