package repository

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

var (
	ErrObjectNotFound = errors.New("object not found")
)

type ObjectRepository interface {
	// Основные операции
	Create(object *models.Object) error
	GetByID(id uint) (*models.Object, error)
	Update(id uint, updates *models.ObjectUpdateRequest) error
	Delete(id uint) error
	List(filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error)
	
	// Специфичные операции
	GetByOwner(ownerID uint, filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error)
	UpdateStatus(id uint, status models.ObjectStatus) error
	IncrementViewCount(id uint) error
	UpdateRating(id uint, rating float64, reviewCount int) error
	
	// Работа с изображениями
	AddImage(objectID uint, image *models.ObjectImage) error
	RemoveImage(objectID uint, imageID uint) error
	SetPrimaryImage(objectID uint, imageID uint) error
	GetImages(objectID uint) ([]models.ObjectImage, error)
	
	// Работа с удобствами
	AddAmenities(objectID uint, amenityIDs []uint) error
	RemoveAmenities(objectID uint, amenityIDs []uint) error
	GetAmenities(objectID uint) ([]models.Amenity, error)
}

type ObjectFilter struct {
	Type        []models.ObjectType
	City        string
	Status      []models.ObjectStatus
	OwnerID     uint
	MinPrice    float64
	MaxPrice    float64
	MinRating   float64
	AmenityIDs  []uint
	Search      string
}

type Pagination struct {
	Page     int `form:"page" default:"1"`
	PageSize int `form:"page_size" default:"20"`
}

type objectRepository struct {
	db *gorm.DB
}

func NewObjectRepository(db *gorm.DB) ObjectRepository {
	return &objectRepository{db: db}
}

// Create создает новый объект
func (r *objectRepository) Create(object *models.Object) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Создаем основной объект
		if err := tx.Create(object).Error; err != nil {
			return err
		}
		
		// Добавляем связи с удобствами, если они есть
		if len(object.Amenities) > 0 {
			if err := tx.Model(object).Association("Amenities").Append(object.Amenities); err != nil {
				return err
			}
		}
		
		return nil
	})
}

// GetByID возвращает объект по ID с связанными данными
func (r *objectRepository) GetByID(id uint) (*models.Object, error) {
	var object models.Object
	err := r.db.
		Preload("Owner", func(db *gorm.DB) *gorm.DB {
			return db.Select("id, first_name, last_name, email, phone")
		}).
		Preload("Images", func(db *gorm.DB) *gorm.DB {
			return db.Order("is_primary DESC, order ASC")
		}).
		Preload("Amenities").
		First(&object, id).Error
	
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrObjectNotFound
		}
		return nil, err
	}
	
	return &object, nil
}

// Update обновляет объект
func (r *objectRepository) Update(id uint, updates *models.ObjectUpdateRequest) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Обновляем основные поля
		updateData := map[string]interface{}{}
		
		if updates.Title != "" {
			updateData["title"] = updates.Title
		}
		if updates.Type != "" {
			updateData["type"] = updates.Type
		}
		if updates.Description != "" {
			updateData["description"] = updates.Description
		}
		if updates.City != "" {
			updateData["city"] = updates.City
		}
		if updates.Address != "" {
			updateData["address"] = updates.Address
		}
		if updates.Latitude != 0 {
			updateData["latitude"] = updates.Latitude
		}
		if updates.Longitude != 0 {
			updateData["longitude"] = updates.Longitude
		}
		if updates.Price != 0 {
			updateData["price"] = updates.Price
		}
		if updates.PricePeriod != "" {
			updateData["price_period"] = updates.PricePeriod
		}
		if updates.Status != "" {
			updateData["status"] = updates.Status
		}
		
		if len(updateData) > 0 {
			if err := tx.Model(&models.Object{}).Where("id = ?", id).Updates(updateData).Error; err != nil {
				return err
			}
		}
		
		// Обновляем удобства, если переданы
		if updates.AmenityIDs != nil {
			var object models.Object
			if err := tx.First(&object, id).Error; err != nil {
				return err
			}
			
			var amenities []models.Amenity
			if err := tx.Where("id IN ?", updates.AmenityIDs).Find(&amenities).Error; err != nil {
				return err
			}
			
			if err := tx.Model(&object).Association("Amenities").Replace(amenities); err != nil {
				return err
			}
		}
		
		return nil
	})
}

// Delete удаляет объект (мягкое удаление)
func (r *objectRepository) Delete(id uint) error {
	result := r.db.Delete(&models.Object{}, id)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return ErrObjectNotFound
	}
	return nil
}

// List возвращает список объектов с фильтрацией и пагинацией
func (r *objectRepository) List(filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error) {
	var objects []models.Object
	var total int64
	
	query := r.db.Model(&models.Object{})
	
	// Применяем фильтры
	if filter != nil {
		query = r.applyFilters(query, filter)
	}
	
	// Считаем общее количество
	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("Images", func(db *gorm.DB) *gorm.DB {
			return db.Where("is_primary = ?", true).Limit(1)
		}).
		Preload("Amenities").
		Order("created_at DESC").
		Find(&objects).Error
	
	if err != nil {
		return nil, 0, err
	}
	
	return objects, total, nil
}

// GetByOwner возвращает объекты владельца
func (r *objectRepository) GetByOwner(ownerID uint, filter *ObjectFilter, pagination *Pagination) ([]models.Object, int64, error) {
	if filter == nil {
		filter = &ObjectFilter{}
	}
	filter.OwnerID = ownerID
	return r.List(filter, pagination)
}

// UpdateStatus обновляет статус объекта
func (r *objectRepository) UpdateStatus(id uint, status models.ObjectStatus) error {
	result := r.db.Model(&models.Object{}).Where("id = ?", id).Update("status", status)
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return ErrObjectNotFound
	}
	return nil
}

// IncrementViewCount увеличивает счетчик просмотров
func (r *objectRepository) IncrementViewCount(id uint) error {
	return r.db.Model(&models.Object{}).
		Where("id = ?", id).
		Update("view_count", gorm.Expr("view_count + ?", 1)).
		Error
}

// UpdateRating обновляет рейтинг и количество отзывов
func (r *objectRepository) UpdateRating(id uint, rating float64, reviewCount int) error {
	return r.db.Model(&models.Object{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"rating":       rating,
			"review_count": reviewCount,
		}).Error
}

// AddImage добавляет изображение к объекту
func (r *objectRepository) AddImage(objectID uint, image *models.ObjectImage) error {
	image.ObjectID = objectID
	return r.db.Create(image).Error
}

// RemoveImage удаляет изображение объекта
func (r *objectRepository) RemoveImage(objectID uint, imageID uint) error {
	result := r.db.Where("object_id = ? AND id = ?", objectID, imageID).Delete(&models.ObjectImage{})
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return ErrObjectNotFound
	}
	return nil
}

// SetPrimaryImage устанавливает основное изображение
func (r *objectRepository) SetPrimaryImage(objectID uint, imageID uint) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		// Сбрасываем все is_primary для объекта
		if err := tx.Model(&models.ObjectImage{}).
			Where("object_id = ?", objectID).
			Update("is_primary", false).Error; err != nil {
			return err
		}
		
		// Устанавливаем новое основное изображение
		result := tx.Model(&models.ObjectImage{}).
			Where("object_id = ? AND id = ?", objectID, imageID).
			Update("is_primary", true)
		
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return ErrObjectNotFound
		}
		
		return nil
	})
}

// GetImages возвращает изображения объекта
func (r *objectRepository) GetImages(objectID uint) ([]models.ObjectImage, error) {
	var images []models.ObjectImage
	err := r.db.Where("object_id = ?", objectID).
		Order("is_primary DESC, order ASC").
		Find(&images).Error
	return images, err
}

// AddAmenities добавляет удобства к объекту
func (r *objectRepository) AddAmenities(objectID uint, amenityIDs []uint) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		var object models.Object
		if err := tx.First(&object, objectID).Error; err != nil {
			return err
		}
		
		var amenities []models.Amenity
		if err := tx.Where("id IN ?", amenityIDs).Find(&amenities).Error; err != nil {
			return err
		}
		
		return tx.Model(&object).Association("Amenities").Append(amenities)
	})
}

// RemoveAmenities удаляет удобства у объекта
func (r *objectRepository) RemoveAmenities(objectID uint, amenityIDs []uint) error {
	return r.db.Transaction(func(tx *gorm.DB) error {
		var object models.Object
		if err := tx.First(&object, objectID).Error; err != nil {
			return err
		}
		
		var amenities []models.Amenity
		if err := tx.Where("id IN ?", amenityIDs).Find(&amenities).Error; err != nil {
			return err
		}
		
		return tx.Model(&object).Association("Amenities").Delete(amenities)
	})
}

// GetAmenities возвращает удобства объекта
func (r *objectRepository) GetAmenities(objectID uint) ([]models.Amenity, error) {
	var amenities []models.Amenity
	err := r.db.Joins("JOIN object_amenities ON amenities.id = object_amenities.amenity_id").
		Where("object_amenities.object_id = ?", objectID).
		Find(&amenities).Error
	return amenities, err
}

// applyFilters применяет фильтры к запросу
func (r *objectRepository) applyFilters(query *gorm.DB, filter *ObjectFilter) *gorm.DB {
	if len(filter.Type) > 0 {
		query = query.Where("type IN ?", filter.Type)
	}
	
	if filter.City != "" {
		query = query.Where("city = ?", filter.City)
	}
	
	if len(filter.Status) > 0 {
		query = query.Where("status IN ?", filter.Status)
	}
	
	if filter.OwnerID != 0 {
		query = query.Where("owner_id = ?", filter.OwnerID)
	}
	
	if filter.MinPrice > 0 {
		query = query.Where("price >= ?", filter.MinPrice)
	}
	
	if filter.MaxPrice > 0 {
		query = query.Where("price <= ?", filter.MaxPrice)
	}
	
	if filter.MinRating > 0 {
		query = query.Where("rating >= ?", filter.MinRating)
	}
	
	if filter.Search != "" {
		search := "%" + filter.Search + "%"
		query = query.Where("title ILIKE ? OR description ILIKE ?", search, search)
	}
	
	// Фильтр по удобствам
	if len(filter.AmenityIDs) > 0 {
		query = query.Joins("JOIN object_amenities ON objects.id = object_amenities.object_id").
			Where("object_amenities.amenity_id IN ?", filter.AmenityIDs)
	}
	
	return query
}