package service

import (
	"errors"
	"narasi-ahli-be/app/database/entity"
	"narasi-ahli-be/app/module/ebooks/mapper"
	"narasi-ahli-be/app/module/ebooks/repository"
	"narasi-ahli-be/app/module/ebooks/request"
	"narasi-ahli-be/app/module/ebooks/response"
	usersRepository "narasi-ahli-be/app/module/users/repository"
	"narasi-ahli-be/utils/paginator"
	utilSvc "narasi-ahli-be/utils/service"

	"github.com/rs/zerolog"
)

// EbookRatingsService
type ebookRatingsService struct {
	Repo               repository.EbookRatingsRepository
	EbooksRepo         repository.EbooksRepository
	EbookPurchasesRepo repository.EbookPurchasesRepository
	Log                zerolog.Logger
	UsersRepo          usersRepository.UsersRepository
}

// EbookRatingsService define interface of IEbookRatingsService
type EbookRatingsService interface {
	GetAll(req request.EbookRatingsQueryRequest) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error)
	GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error)
	GetByUserId(authToken string, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error)
	GetEbookRatingSummary(ebookId uint) (summary *response.EbookRatingSummaryResponse, err error)
	Create(req request.EbookRatingsCreateRequest, authToken string) (rating *response.EbookRatingsResponse, err error)
	Update(id uint, req request.EbookRatingsUpdateRequest, authToken string) (err error)
	Delete(id uint, authToken string) error
	GetRatingStats(ebookId uint) (stats *response.EbookRatingStatsResponse, err error)
}

// NewEbookRatingsService init EbookRatingsService
func NewEbookRatingsService(
	repo repository.EbookRatingsRepository,
	ebooksRepo repository.EbooksRepository,
	ebookPurchasesRepo repository.EbookPurchasesRepository,
	log zerolog.Logger,
	usersRepo usersRepository.UsersRepository) EbookRatingsService {

	return &ebookRatingsService{
		Repo:               repo,
		EbooksRepo:         ebooksRepo,
		EbookPurchasesRepo: ebookPurchasesRepo,
		Log:                log,
		UsersRepo:          usersRepo,
	}
}

// GetAll implement interface of EbookRatingsService
func (_i *ebookRatingsService) GetAll(req request.EbookRatingsQueryRequest) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) {
	ratingsData, paging, err := _i.Repo.GetAll(req)
	if err != nil {
		return nil, paging, err
	}

	ratings = mapper.ToEbookRatingsResponseList(ratingsData)

	return ratings, paging, nil
}

// GetByEbookId implement interface of EbookRatingsService
func (_i *ebookRatingsService) GetByEbookId(ebookId uint, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) {
	ratingsData, paging, err := _i.Repo.GetByEbookId(ebookId, pagination)
	if err != nil {
		return nil, paging, err
	}

	ratings = mapper.ToEbookRatingsResponseList(ratingsData)

	return ratings, paging, nil
}

// GetByUserId implement interface of EbookRatingsService
func (_i *ebookRatingsService) GetByUserId(authToken string, pagination *paginator.Pagination) (ratings []*response.EbookRatingsResponse, paging paginator.Pagination, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return nil, paginator.Pagination{}, errors.New("user not found")
	}

	ratingsData, paging, err := _i.Repo.GetByUserId(user.ID, pagination)
	if err != nil {
		return nil, paging, err
	}

	ratings = mapper.ToEbookRatingsResponseList(ratingsData)

	return ratings, paging, nil
}

// GetEbookRatingSummary implement interface of EbookRatingsService
func (_i *ebookRatingsService) GetEbookRatingSummary(ebookId uint) (summary *response.EbookRatingSummaryResponse, err error) {
	// Get ebook info
	ebook, err := _i.EbooksRepo.FindOne(ebookId)
	if err != nil {
		return nil, errors.New("ebook not found")
	}

	// Get rating stats
	totalRatings, averageRating, ratingCounts, err := _i.Repo.GetEbookRatingStats(ebookId)
	if err != nil {
		return nil, err
	}

	// Get recent reviews
	recentReviews, err := _i.Repo.GetRecentReviews(ebookId, 5)
	if err != nil {
		return nil, err
	}

	summary = mapper.ToEbookRatingSummaryResponse(ebookId, ebook.Title, averageRating, totalRatings, ratingCounts, recentReviews)

	return summary, nil
}

// Create implement interface of EbookRatingsService
func (_i *ebookRatingsService) Create(req request.EbookRatingsCreateRequest, authToken string) (rating *response.EbookRatingsResponse, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return nil, errors.New("user not found")
	}

	// Check if ebook exists
	_, err = _i.EbooksRepo.FindOne(req.EbookId)
	if err != nil {
		return nil, errors.New("ebook not found")
	}

	// Check if purchase exists and belongs to user
	purchase, err := _i.EbookPurchasesRepo.FindOne(req.PurchaseId)
	if err != nil {
		return nil, errors.New("purchase not found")
	}

	if purchase.BuyerId != user.ID {
		return nil, errors.New("purchase does not belong to user")
	}

	if purchase.EbookId != req.EbookId {
		return nil, errors.New("purchase does not match ebook")
	}

	if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" {
		return nil, errors.New("payment not completed")
	}

	// Check if user already rated this ebook
	existingRating, err := _i.Repo.FindByUserAndEbook(user.ID, req.EbookId)
	if err == nil && existingRating != nil {
		return nil, errors.New("user already rated this ebook")
	}

	// Check if user already rated this purchase
	existingPurchaseRating, err := _i.Repo.FindByPurchaseId(req.PurchaseId)
	if err == nil && existingPurchaseRating != nil {
		return nil, errors.New("user already rated this purchase")
	}

	// Create rating
	ratingEntity := req.ToEntity()
	ratingEntity.UserId = user.ID

	ratingData, err := _i.Repo.Create(ratingEntity)
	if err != nil {
		return nil, err
	}

	// Update ebook rating stats
	err = _i.updateEbookRatingStats(req.EbookId)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to update ebook rating stats")
	}

	rating = mapper.ToEbookRatingsResponse(ratingData)

	return rating, nil
}

// Update implement interface of EbookRatingsService
func (_i *ebookRatingsService) Update(id uint, req request.EbookRatingsUpdateRequest, authToken string) (err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return errors.New("user not found")
	}

	// Check if rating exists and belongs to user
	existingRating, err := _i.Repo.FindOne(id)
	if err != nil {
		return errors.New("rating not found")
	}

	if existingRating.UserId != user.ID {
		return errors.New("rating does not belong to user")
	}

	// Update rating
	ratingEntity := req.ToEntity()
	err = _i.Repo.Update(id, ratingEntity)
	if err != nil {
		return err
	}

	// Update ebook rating stats
	err = _i.updateEbookRatingStats(existingRating.EbookId)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to update ebook rating stats")
	}

	return nil
}

// Delete implement interface of EbookRatingsService
func (_i *ebookRatingsService) Delete(id uint, authToken string) error {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return errors.New("user not found")
	}

	// Check if rating exists and belongs to user
	existingRating, err := _i.Repo.FindOne(id)
	if err != nil {
		return errors.New("rating not found")
	}

	if existingRating.UserId != user.ID {
		return errors.New("rating does not belong to user")
	}

	// Delete rating
	err = _i.Repo.Delete(id)
	if err != nil {
		return err
	}

	// Update ebook rating stats
	err = _i.updateEbookRatingStats(existingRating.EbookId)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to update ebook rating stats")
	}

	return nil
}

// GetRatingStats implement interface of EbookRatingsService
func (_i *ebookRatingsService) GetRatingStats(ebookId uint) (stats *response.EbookRatingStatsResponse, err error) {
	totalRatings, averageRating, ratingCounts, err := _i.Repo.GetEbookRatingStats(ebookId)
	if err != nil {
		return nil, err
	}

	stats = mapper.ToEbookRatingStatsResponse(totalRatings, averageRating, ratingCounts)

	return stats, nil
}

// updateEbookRatingStats updates the rating statistics in the ebook table
func (_i *ebookRatingsService) updateEbookRatingStats(ebookId uint) error {
	totalRatings, averageRating, _, err := _i.Repo.GetEbookRatingStats(ebookId)
	if err != nil {
		return err
	}

	// Update ebook with new rating stats
	ebookUpdate := &entity.Ebooks{
		Rating:      &averageRating,
		ReviewCount: &totalRatings,
	}

	err = _i.EbooksRepo.UpdateSkipNull(ebookId, ebookUpdate)
	if err != nil {
		return err
	}

	return nil
}
