package repository

import (
	"errors"
	"gorm.io/gorm"
	"api_es/internal/models"
)

var (
	ErrReviewNotFound  = errors.New("review not found")
	ErrDuplicateReview = errors.New("user already has review for this object")
)

type ReviewRepository interface {
	// Основные операции
	Create(review *models.Review) error
	GetByID(id uint) (*models.Review, error)
	Update(id uint, updates map[string]interface{}) error
	Delete(id uint) error

	// Списки отзывов
	GetByObject(objectID uint, pagination *Pagination) ([]models.Review, int64, error)
	GetByAuthor(authorID uint, pagination *Pagination) ([]models.Review, int64, error)
	GetByObjectAndAuthor(objectID, authorID uint) (*models.Review, error)

	// Статистика
	GetObjectRatingStats(objectID uint) (float64, int, error)
	GetUserReviewStats(authorID uint) (int, float64, error)

	// Административные методы
	SetActive(id uint, isActive bool) error
	GetAll(pagination *Pagination, filters *ReviewFilter) ([]models.Review, int64, error)
}

type ReviewFilter struct {
	ObjectID  uint
	AuthorID  uint
	Rating    int
	IsActive  *bool
	MinRating int
	MaxRating int
}

type reviewRepository struct {
	db *gorm.DB
}

func NewReviewRepository(db *gorm.DB) ReviewRepository {
	return &reviewRepository{db: db}
}

// Create создает новый отзыв
func (r *reviewRepository) Create(review *models.Review) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Проверяем, не оставлял ли пользователь уже отзыв на этот объект
		var existingReview models.Review
		err := tx.Where("object_id = ? AND author_id = ?", review.ObjectID, review.AuthorID).
			First(&existingReview).Error

		if err == nil {
			return ErrDuplicateReview
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			return err
		}

		// Создаем отзыв
		if err := tx.Create(review).Error; err != nil {
			return err
		}

		// Обновляем рейтинг объекта
		return r.updateObjectRating(tx, review.ObjectID)
	})
}

// GetByID возвращает отзыв по ID
func (r *reviewRepository) GetByID(id uint) (*models.Review, error) {
	var review models.Review
	err := r.db.
		Preload("Author", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, first_name, last_name, avatar")
		}).
		Preload("Object", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, title, type")
		}).
		First(&review, id).Error

	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrReviewNotFound
		}
		return nil, err
	}

	return &review, nil
}

// Update обновляет отзыв
func (r *reviewRepository) Update(id uint, updates map[string]interface{}) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Получаем отзыв для получения object_id
		var review models.Review
		if err := tx.Select("object_id").First(&review, id).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return ErrReviewNotFound
			}
			return err
		}

		// Обновляем отзыв
		result := tx.Model(&models.Review{}).Where("id = ?", id).Updates(updates)
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return ErrReviewNotFound
		}

		// Обновляем рейтинг объекта, если изменился рейтинг
		if _, hasRating := updates["rating"]; hasRating {
			return r.updateObjectRating(tx, review.ObjectID)
		}

		return nil
	})
}

// Delete удаляет отзыв
func (r *reviewRepository) Delete(id uint) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Получаем отзыв для получения object_id
		var review models.Review
		if err := tx.Select("object_id").First(&review, id).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return ErrReviewNotFound
			}
			return err
		}

		// Удаляем отзыв
		result := tx.Delete(&models.Review{}, id)
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return ErrReviewNotFound
		}

		// Обновляем рейтинг объекта
		return r.updateObjectRating(tx, review.ObjectID)
	})
}

// GetByObject возвращает отзывы для объекта
func (r *reviewRepository) GetByObject(objectID uint, pagination *Pagination) ([]models.Review, int64, error) {
	var reviews []models.Review
	var total int64

	query := r.db.Model(&models.Review{}).Where("object_id = ? AND is_active = ?", objectID, true)

	// Считаем общее количество
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	// Применяем пагинацию
	if pagination != nil {
		offset := (pagination.Page - 1) * pagination.PageSize
		query = query.Offset(offset).Limit(pagination.PageSize)
	}

	// Загружаем данные
	err := query.
		Preload("Author", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, first_name, last_name, avatar")
		}).
		Order("created_at DESC").
		Find(&reviews).Error

	if err != nil {
		return nil, 0, err
	}

	return reviews, total, nil
}

// GetByAuthor возвращает отзывы пользователя
func (r *reviewRepository) GetByAuthor(authorID uint, pagination *Pagination) ([]models.Review, int64, error) {
	var reviews []models.Review
	var total int64

	query := r.db.Model(&models.Review{}).Where("author_id = ?", authorID)

	// Считаем общее количество
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	// Применяем пагинацию
	if pagination != nil {
		offset := (pagination.Page - 1) * pagination.PageSize
		query = query.Offset(offset).Limit(pagination.PageSize)
	}

	// Загружаем данные
	err := query.
		Preload("Object", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, title, type, city")
		}).
		Order("created_at DESC").
		Find(&reviews).Error

	if err != nil {
		return nil, 0, err
	}

	return reviews, total, nil
}

// GetByObjectAndAuthor возвращает отзыв конкретного пользователя для объекта
func (r *reviewRepository) GetByObjectAndAuthor(objectID, authorID uint) (*models.Review, error) {
	var review models.Review
	err := r.db.
		Where("object_id = ? AND author_id = ?", objectID, authorID).
		First(&review).Error

	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrReviewNotFound
		}
		return nil, err
	}

	return &review, nil
}

// GetObjectRatingStats возвращает статистику рейтинга для объекта
func (r *reviewRepository) GetObjectRatingStats(objectID uint) (float64, int, error) {
	var stats struct {
		AverageRating float64
		ReviewCount   int
	}

	err := r.db.Model(&models.Review{}).
		Select("AVG(rating) as average_rating, COUNT(*) as review_count").
		Where("object_id = ? AND is_active = ?", objectID, true).
		Scan(&stats).Error

	if err != nil {
		return 0, 0, err
	}

	return stats.AverageRating, stats.ReviewCount, nil
}

// GetUserReviewStats возвращает статистику отзывов пользователя
func (r *reviewRepository) GetUserReviewStats(authorID uint) (int, float64, error) {
	var stats struct {
		ReviewCount   int
		AverageRating float64
	}

	err := r.db.Model(&models.Review{}).
		Select("COUNT(*) as review_count, AVG(rating) as average_rating").
		Where("author_id = ? AND is_active = ?", authorID, true).
		Scan(&stats).Error

	if err != nil {
		return 0, 0, err
	}

	return stats.ReviewCount, stats.AverageRating, nil
}

// SetActive активирует/деактивирует отзыв
func (r *reviewRepository) SetActive(id uint, isActive bool) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Получаем отзыв для получения object_id
		var review models.Review
		if err := tx.Select("object_id").First(&review, id).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return ErrReviewNotFound
			}
			return err
		}

		// Обновляем статус
		result := tx.Model(&models.Review{}).Where("id = ?", id).Update("is_active", isActive)
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return ErrReviewNotFound
		}

		// Обновляем рейтинг объекта
		return r.updateObjectRating(tx, review.ObjectID)
	})
}

// GetAll возвращает все отзывы с фильтрацией (для админки)
func (r *reviewRepository) GetAll(pagination *Pagination, filters *ReviewFilter) ([]models.Review, int64, error) {
	var reviews []models.Review
	var total int64

	query := r.db.Model(&models.Review{})

	// Применяем фильтры
	if filters != nil {
		query = r.applyFilters(query, filters)
	}

	// Считаем общее количество
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	// Применяем пагинацию
	if pagination != nil {
		offset := (pagination.Page - 1) * pagination.PageSize
		query = query.Offset(offset).Limit(pagination.PageSize)
	}

	// Загружаем данные
	err := query.
		Preload("Author", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, first_name, last_name, email")
		}).
		Preload("Object", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, title, type")
		}).
		Order("created_at DESC").
		Find(&reviews).Error

	if err != nil {
		return nil, 0, err
	}

	return reviews, total, nil
}

// updateObjectRating обновляет рейтинг объекта
func (r *reviewRepository) updateObjectRating(tx *gorm.DB, objectID uint) error {
	stats, _, err := r.GetObjectRatingStats(objectID)
	if err != nil {
		return err
	}
	
	count_ := int64(0)

	// Обновляем рейтинг объекта
	return tx.Model(&models.Object{}).
		Where("id = ?", objectID).
		Updates(map[string]interface{}{
			"rating": stats,
			"review_count": tx.Model(&models.Review{}).
				Where("object_id = ? AND is_active = ?", objectID, true).
				Count(&count_),
		}).Error
}

// applyFilters применяет фильтры к запросу
func (r *reviewRepository) applyFilters(query *gorm.DB, filters *ReviewFilter) *gorm.DB {
	if filters.ObjectID != 0 {
		query = query.Where("object_id = ?", filters.ObjectID)
	}

	if filters.AuthorID != 0 {
		query = query.Where("author_id = ?", filters.AuthorID)
	}

	if filters.Rating != 0 {
		query = query.Where("rating = ?", filters.Rating)
	}

	if filters.IsActive != nil {
		query = query.Where("is_active = ?", *filters.IsActive)
	}

	if filters.MinRating > 0 {
		query = query.Where("rating >= ?", filters.MinRating)
	}

	if filters.MaxRating > 0 {
		query = query.Where("rating <= ?", filters.MaxRating)
	}

	return query
}
