package comment

import (
	"api_yal/internal/logger"
	"api_yal/internal/models"
	"api_yal/internal/repository"
	"errors"
	"fmt"

	"gorm.io/gorm"
	"go.uber.org/zap"
)

type CommentService interface {
	CreateComment(userID uint, req *CreateCommentRequest) (*CommentResponse, error)
	GetCommentByID(id uint) (*CommentResponse, error)
	UpdateComment(id uint, userID uint, req *UpdateCommentRequest) (*CommentResponse, error)
	DeleteComment(id uint, userID uint, isAdmin bool) error
	ListComments(req *ListCommentsRequest) ([]CommentResponse, int64, error)
	GetCommentsByFeedback(feedbackID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error)
	GetCommentsByAuthor(authorID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error)
	GetReplies(parentID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error)
	VerifyComment(id uint, verified bool) error
	GetCommentStats() (map[string]interface{}, error)
}

type CommentServiceImpl struct {
	commentRepo repository.CommentRepository
	db          *gorm.DB
	logger      *zap.Logger
}

func NewCommentServiceImpl(commentRepo repository.CommentRepository, db *gorm.DB) *CommentServiceImpl {
	return &CommentServiceImpl{
		commentRepo: commentRepo,
		db:          db,
		logger:      logger.Get(),
	}
}

func (s *CommentServiceImpl) CreateComment(userID uint, req *CreateCommentRequest) (*CommentResponse, error) {
	s.logger.Info("Creating new comment",
		zap.Uint("user_id", userID),
		zap.Uint("feedback_id", req.FeedbackID),
		zap.Uint("parent_id", *req.ParentID),
	)

	comment := &models.Comment{
		AuthorID:   userID,
		FeedbackID: req.FeedbackID,
		Text:       req.Text,
		ParentID:   req.ParentID,
		IsVerified: false, // можно настроить автоматическую верификацию
	}

	err := s.commentRepo.Create(comment)
	if err != nil {
		s.logger.Error("Failed to create comment",
			zap.Error(err),
			zap.Uint("user_id", userID),
		)
		return nil, fmt.Errorf("failed to create comment: %w", err)
	}

	// Загружаем полную информацию о комментарии
	createdComment, err := s.commentRepo.GetByID(comment.ID)
	if err != nil {
		s.logger.Error("Failed to get created comment",
			zap.Error(err),
			zap.Uint("comment_id", comment.ID),
		)
		return nil, fmt.Errorf("failed to get created comment: %w", err)
	}

	return s.mapToResponse(createdComment), nil
}

func (s *CommentServiceImpl) GetCommentByID(id uint) (*CommentResponse, error) {
	s.logger.Info("Getting comment by ID",
		zap.Uint("comment_id", id),
	)

	comment, err := s.commentRepo.GetByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("comment not found")
		}
		s.logger.Error("Failed to get comment",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return nil, fmt.Errorf("failed to get comment: %w", err)
	}

	return s.mapToResponse(comment), nil
}

func (s *CommentServiceImpl) UpdateComment(id uint, userID uint, req *UpdateCommentRequest) (*CommentResponse, error) {
	s.logger.Info("Updating comment",
		zap.Uint("comment_id", id),
		zap.Uint("user_id", userID),
	)

	comment, err := s.commentRepo.GetByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.New("comment not found")
		}
		s.logger.Error("Failed to get comment for update",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return nil, fmt.Errorf("failed to get comment: %w", err)
	}

	// Проверяем права: только автор может редактировать
	if comment.AuthorID != userID {
		s.logger.Warn("Unauthorized update attempt",
			zap.Uint("comment_id", id),
			zap.Uint("author_id", comment.AuthorID),
			zap.Uint("user_id", userID),
		)
		return nil, errors.New("you can only edit your own comments")
	}

	comment.Text = req.Text
	err = s.commentRepo.Update(comment)
	if err != nil {
		s.logger.Error("Failed to update comment",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return nil, fmt.Errorf("failed to update comment: %w", err)
	}

	// Отмечаем как отредактированный
	err = s.commentRepo.MarkAsEdited(id)
	if err != nil {
		s.logger.Warn("Failed to mark comment as edited",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
	}

	updatedComment, err := s.commentRepo.GetByID(id)
	if err != nil {
		s.logger.Error("Failed to get updated comment",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return nil, fmt.Errorf("failed to get updated comment: %w", err)
	}

	return s.mapToResponse(updatedComment), nil
}

func (s *CommentServiceImpl) DeleteComment(id uint, userID uint, isAdmin bool) error {
	s.logger.Info("Deleting comment",
		zap.Uint("comment_id", id),
		zap.Uint("user_id", userID),
		zap.Bool("is_admin", isAdmin),
	)

	comment, err := s.commentRepo.GetByID(id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return errors.New("comment not found")
		}
		s.logger.Error("Failed to get comment for deletion",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return fmt.Errorf("failed to get comment: %w", err)
	}

	// Проверяем права: автор или администратор
	if !isAdmin && comment.AuthorID != userID {
		s.logger.Warn("Unauthorized delete attempt",
			zap.Uint("comment_id", id),
			zap.Uint("author_id", comment.AuthorID),
			zap.Uint("user_id", userID),
			zap.Bool("is_admin", isAdmin),
		)
		return errors.New("you can only delete your own comments")
	}

	err = s.commentRepo.Delete(id)
	if err != nil {
		s.logger.Error("Failed to delete comment",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return fmt.Errorf("failed to delete comment: %w", err)
	}

	return nil
}

func (s *CommentServiceImpl) ListComments(req *ListCommentsRequest) ([]CommentResponse, int64, error) {
	s.logger.Info("Listing comments",
		zap.Int("page", req.Page),
		zap.Int("page_size", req.PageSize),
	)

	offset := (req.Page - 1) * req.PageSize
	limit := req.PageSize

	// Базовый запрос для подсчета
	query := s.db.Model(&models.Comment{})

	// Применяем фильтры
	if req.FeedbackID != nil {
		query = query.Where("feedback_id = ?", *req.FeedbackID)
	}
	if req.AuthorID != nil {
		query = query.Where("author_id = ?", *req.AuthorID)
	}
	if req.ParentID != nil {
		if *req.ParentID == 0 {
			query = query.Where("parent_id IS NULL")
		} else {
			query = query.Where("parent_id = ?", *req.ParentID)
		}
	}
	if req.Verified != nil {
		query = query.Where("is_verified = ?", *req.Verified)
	}

	// Подсчитываем общее количество
	var total int64
	err := query.Count(&total).Error
	if err != nil {
		s.logger.Error("Failed to count comments",
			zap.Error(err),
		)
		return nil, 0, fmt.Errorf("failed to count comments: %w", err)
	}

	// Получаем комментарии
	comments, err := s.commentRepo.List(offset, limit)
	if err != nil {
		s.logger.Error("Failed to list comments",
			zap.Error(err),
		)
		return nil, 0, fmt.Errorf("failed to list comments: %w", err)
	}

	// Фильтруем и маппим результаты
	var responses []CommentResponse
	for i := range comments {
		// Применяем фильтры в памяти (так как репозиторий возвращает все)
		if s.shouldIncludeComment(&comments[i], req) {
			responses = append(responses, *s.mapToResponse(&comments[i]))
		}
	}

	return responses, total, nil
}

func (s *CommentServiceImpl) GetCommentsByFeedback(feedbackID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error) {
	s.logger.Info("Getting comments by feedback",
		zap.Uint("feedback_id", feedbackID),
		zap.Int("page", req.Page),
		zap.Int("page_size", req.PageSize),
	)

	offset := (req.Page - 1) * req.PageSize
	limit := req.PageSize

	comments, err := s.commentRepo.ListByFeedback(feedbackID, offset, limit)
	if err != nil {
		s.logger.Error("Failed to get comments by feedback",
			zap.Error(err),
			zap.Uint("feedback_id", feedbackID),
		)
		return nil, 0, fmt.Errorf("failed to get comments by feedback: %w", err)
	}

	// Подсчитываем общее количество
	var total int64
	err = s.db.Model(&models.Comment{}).Where("feedback_id = ?", feedbackID).Count(&total).Error
	if err != nil {
		s.logger.Error("Failed to count comments by feedback",
			zap.Error(err),
			zap.Uint("feedback_id", feedbackID),
		)
		return nil, 0, fmt.Errorf("failed to count comments: %w", err)
	}

	responses := make([]CommentResponse, len(comments))
	for i := range comments {
		responses[i] = *s.mapToResponse(&comments[i])
	}

	return responses, total, nil
}

func (s *CommentServiceImpl) GetCommentsByAuthor(authorID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error) {
	s.logger.Info("Getting comments by author",
		zap.Uint("author_id", authorID),
		zap.Int("page", req.Page),
		zap.Int("page_size", req.PageSize),
	)

	offset := (req.Page - 1) * req.PageSize
	limit := req.PageSize

	comments, err := s.commentRepo.ListByAuthor(authorID, offset, limit)
	if err != nil {
		s.logger.Error("Failed to get comments by author",
			zap.Error(err),
			zap.Uint("author_id", authorID),
		)
		return nil, 0, fmt.Errorf("failed to get comments by author: %w", err)
	}

	// Подсчитываем общее количество
	var total int64
	err = s.db.Model(&models.Comment{}).Where("author_id = ?", authorID).Count(&total).Error
	if err != nil {
		s.logger.Error("Failed to count comments by author",
			zap.Error(err),
			zap.Uint("author_id", authorID),
		)
		return nil, 0, fmt.Errorf("failed to count comments: %w", err)
	}

	responses := make([]CommentResponse, len(comments))
	for i := range comments {
		responses[i] = *s.mapToResponse(&comments[i])
	}

	return responses, total, nil
}

func (s *CommentServiceImpl) GetReplies(parentID uint, req *ListCommentsRequest) ([]CommentResponse, int64, error) {
	s.logger.Info("Getting replies for comment",
		zap.Uint("parent_id", parentID),
		zap.Int("page", req.Page),
		zap.Int("page_size", req.PageSize),
	)

	offset := (req.Page - 1) * req.PageSize
	limit := req.PageSize

	replies, err := s.commentRepo.ListReplies(parentID, offset, limit)
	if err != nil {
		s.logger.Error("Failed to get replies",
			zap.Error(err),
			zap.Uint("parent_id", parentID),
		)
		return nil, 0, fmt.Errorf("failed to get replies: %w", err)
	}

	// Подсчитываем общее количество
	total, err := s.commentRepo.GetRepliesCount(parentID)
	if err != nil {
		s.logger.Error("Failed to count replies",
			zap.Error(err),
			zap.Uint("parent_id", parentID),
		)
		return nil, 0, fmt.Errorf("failed to count replies: %w", err)
	}

	responses := make([]CommentResponse, len(replies))
	for i := range replies {
		responses[i] = *s.mapToResponse(&replies[i])
	}

	return responses, total, nil
}

func (s *CommentServiceImpl) VerifyComment(id uint, verified bool) error {
	s.logger.Info("Verifying comment",
		zap.Uint("comment_id", id),
		zap.Bool("verified", verified),
	)

	err := s.commentRepo.ToggleVerification(id, verified)
	if err != nil {
		s.logger.Error("Failed to verify comment",
			zap.Error(err),
			zap.Uint("comment_id", id),
		)
		return fmt.Errorf("failed to verify comment: %w", err)
	}

	return nil
}

func (s *CommentServiceImpl) GetCommentStats() (map[string]interface{}, error) {
	s.logger.Info("Getting comment statistics")

	var total int64
	err := s.db.Model(&models.Comment{}).Count(&total).Error
	if err != nil {
		s.logger.Error("Failed to count total comments",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to count total comments: %w", err)
	}

	var verified int64
	err = s.db.Model(&models.Comment{}).Where("is_verified = ?", true).Count(&verified).Error
	if err != nil {
		s.logger.Error("Failed to count verified comments",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to count verified comments: %w", err)
	}

	var withReplies int64
	err = s.db.Model(&models.Comment{}).Where("id IN (SELECT DISTINCT parent_id FROM comments WHERE parent_id IS NOT NULL)").Count(&withReplies).Error
	if err != nil {
		s.logger.Error("Failed to count comments with replies",
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to count comments with replies: %w", err)
	}

	stats := map[string]interface{}{
		"total_comments":        total,
		"verified_comments":     verified,
		"unverified_comments":   total - verified,
		"comments_with_replies": withReplies,
	}

	return stats, nil
}

// Вспомогательные методы
func (s *CommentServiceImpl) mapToResponse(comment *models.Comment) *CommentResponse {
	response := &CommentResponse{
		ID:         comment.ID,
		CreatedAt:  comment.CreatedAt,
		UpdatedAt:  comment.UpdatedAt,
		AuthorID:   comment.AuthorID,
		AuthorName: comment.Author.FullName,
		FeedbackID: comment.FeedbackID,
		Text:       comment.Text,
		IsEdited:   comment.IsEdited,
		IsVerified: comment.IsVerified,
	}

	if comment.ParentID != nil {
		response.ParentID = comment.ParentID
	}

	// Добавляем количество ответов, если нужно
	if len(comment.Replies) > 0 {
		response.RepliesCount = int64(len(comment.Replies))
		response.Replies = make([]CommentShortResponse, len(comment.Replies))
		for i, reply := range comment.Replies {
			response.Replies[i] = CommentShortResponse{
				ID:         reply.ID,
				CreatedAt:  reply.CreatedAt,
				AuthorID:   reply.AuthorID,
				AuthorName: reply.Author.FullName,
				Text:       reply.Text,
				IsEdited:   reply.IsEdited,
			}
		}
	} else {
		// Подсчитываем количество ответов, если они не были загружены
		count, _ := s.commentRepo.GetRepliesCount(comment.ID)
		response.RepliesCount = count
	}

	return response
}

func (s *CommentServiceImpl) shouldIncludeComment(comment *models.Comment, req *ListCommentsRequest) bool {
	if req.FeedbackID != nil && comment.FeedbackID != *req.FeedbackID {
		return false
	}
	if req.AuthorID != nil && comment.AuthorID != *req.AuthorID {
		return false
	}
	if req.ParentID != nil {
		if *req.ParentID == 0 && comment.ParentID != nil {
			return false
		}
		if *req.ParentID != 0 && (comment.ParentID == nil || *comment.ParentID != *req.ParentID) {
			return false
		}
	}
	if req.Verified != nil && comment.IsVerified != *req.Verified {
		return false
	}
	return true
}