package repository

import (
	"api_yal/internal/models"
	"gorm.io/gorm"
)

// objectRepositoryImpl реализация интерфейса ObjectRepository
type objectRepositoryImpl struct {
	db *gorm.DB
}

// NewObjectRepository создает новый экземпляр репозитория объектов
func NewObjectRepository(db *gorm.DB) ObjectRepository {
	return &objectRepositoryImpl{db: db}
}

// Create создает новый объект
func (r *objectRepositoryImpl) Create(object *models.Object) error {
	return r.db.Create(object).Error
}

// GetByID возвращает объект по ID
func (r *objectRepositoryImpl) GetByID(id uint) (*models.Object, error) {
	var object models.Object
	err := r.db.Preload("Owner").Preload("TouristRating").Preload("EntrepreneurRating").Preload("Ratings").Preload("Feedbacks").First(&object, id).Error
	if err != nil {
		return nil, err
	}
	return &object, nil
}

// Update обновляет существующий объект
func (r *objectRepositoryImpl) Update(object *models.Object) error {
	return r.db.Save(object).Error
}

// Delete помечает объект как удаленный
func (r *objectRepositoryImpl) Delete(id uint) error {
	return r.db.Delete(&models.Object{}, id).Error
}

// List возвращает список объектов с пагинацией
func (r *objectRepositoryImpl) List(offset, limit int) ([]models.Object, error) {
	var objects []models.Object
	err := r.db.Preload("Owner").Offset(offset).Limit(limit).Find(&objects).Error
	if err != nil {
		return nil, err
	}
	return objects, nil
}

// Count возвращает общее количество объектов
func (r *objectRepositoryImpl) Count() (int64, error) {
	var count int64
	err := r.db.Model(&models.Object{}).Count(&count).Error
	return count, err
}

// ListByOwner возвращает объекты по владельцу
func (r *objectRepositoryImpl) ListByOwner(ownerID uint, offset, limit int) ([]models.Object, error) {
	var objects []models.Object
	err := r.db.Preload("Owner").Where("owner_id = ?", ownerID).Offset(offset).Limit(limit).Find(&objects).Error
	if err != nil {
		return nil, err
	}
	return objects, nil
}

// ListByType возвращает объекты по типу
func (r *objectRepositoryImpl) ListByType(objectType string, offset, limit int) ([]models.Object, error) {
	var objects []models.Object
	err := r.db.Preload("Owner").Where("type = ?", objectType).Offset(offset).Limit(limit).Find(&objects).Error
	if err != nil {
		return nil, err
	}
	return objects, nil
}

// ListByStatus возвращает объекты по статусу
func (r *objectRepositoryImpl) ListByStatus(isActive bool, offset, limit int) ([]models.Object, error) {
	var objects []models.Object
	err := r.db.Preload("Owner").Where("is_active = ?", isActive).Offset(offset).Limit(limit).Find(&objects).Error
	if err != nil {
		return nil, err
	}
	return objects, nil
}

// Search находит объекты по названию, типу или адресу
func (r *objectRepositoryImpl) Search(query string, offset, limit int) ([]models.Object, error) {
	var objects []models.Object
	err := r.db.Preload("Owner").Where("short_name LIKE ? OR long_name LIKE ? OR type LIKE ? OR address LIKE ?", "%"+query+"%", "%"+query+"%", "%"+query+"%", "%"+query+"%").Offset(offset).Limit(limit).Find(&objects).Error
	if err != nil {
		return nil, err
	}
	return objects, nil
}

// GetOwner возвращает владельца объекта
func (r *objectRepositoryImpl) GetOwner(objectID uint) (*models.Account, error) {
	var object models.Object
	err := r.db.Select("owner_id").First(&object, objectID).Error
	if err != nil {
		return nil, err
	}
	return r.getAccountByID(object.OwnerID)
}

// GetTouristRating возвращает туристический рейтинг объекта
func (r *objectRepositoryImpl) GetTouristRating(objectID uint) (*models.Rating, error) {
	return r.getRatingByObjectAndPlatform(objectID, models.PlatformTourist)
}

// GetEntrepreneurRating возвращает рейтинг для предпринимателей
func (r *objectRepositoryImpl) GetEntrepreneurRating(objectID uint) (*models.Rating, error) {
	return r.getRatingByObjectAndPlatform(objectID, models.PlatformEntrepreneur)
}

// GetRatings возвращает все рейтинги объекта
func (r *objectRepositoryImpl) GetRatings(objectID uint) ([]models.Rating, error) {
	var ratings []models.Rating
	err := r.db.Where("object_id = ?", objectID).Find(&ratings).Error
	if err != nil {
		return nil, err
	}
	return ratings, nil
}

// GetFeedbacks возвращает отзывы об объекте
func (r *objectRepositoryImpl) GetFeedbacks(objectID uint, offset, limit int) ([]models.Feedback, error) {
	var feedbacks []models.Feedback
	err := r.db.Preload("Owner").Where("object_id = ?", objectID).Offset(offset).Limit(limit).Find(&feedbacks).Error
	if err != nil {
		return nil, err
	}
	return feedbacks, nil
}

// GetFeedbackCount возвращает количество отзывов об объекте
func (r *objectRepositoryImpl) GetFeedbackCount(objectID uint) (int, error) {
	var count int64
	err := r.db.Model(&models.Feedback{}).Where("object_id = ?", objectID).Count(&count).Error
	return int(count), err
}

// UpdateFeedbackCount обновляет количество отзывов у объекта
func (r *objectRepositoryImpl) UpdateFeedbackCount(objectID uint, count int) error {
	return r.db.Model(&models.Object{}).Where("id = ?", objectID).Update("feedback_count", count).Error
}

// ToggleVerification переключает статус верификации объекта
func (r *objectRepositoryImpl) ToggleVerification(id uint, verified bool) error {
	return r.db.Model(&models.Object{}).Where("id = ?", id).Update("is_verified", verified).Error
}

// GetNearby возвращает объекты в радиусе
func (r *objectRepositoryImpl) GetNearby(latitude, longitude, radius float64, offset, limit int) ([]models.Object, error) {
	var objects []models.Object
	// Используем формулу Haversine для поиска объектов в радиусе
	query := `SELECT *, 
	(6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) AS distance 
	FROM objects 
	HAVING distance < ? 
	ORDER BY distance 
	LIMIT ? OFFSET ?`

	err := r.db.Raw(query, latitude, longitude, latitude, radius, limit, offset).Scan(&objects).Error
	if err != nil {
		return nil, err
	}
	return objects, nil
}

// Вспомогательные методы для получения связанных сущностей
func (r *objectRepositoryImpl) getAccountByID(id uint) (*models.Account, error) {
	var account models.Account
	err := r.db.First(&account, id).Error
	if err != nil {
		return nil, err
	}
	return &account, nil
}

func (r *objectRepositoryImpl) getRatingByObjectAndPlatform(objectID uint, platform models.PlatformType) (*models.Rating, error) {
	var rating models.Rating
	err := r.db.Where("object_id = ? AND platform = ?", objectID, platform).First(&rating).Error
	if err != nil {
		return nil, err
	}
	return &rating, nil
}