package sql

import (
	"context"
	"math"
	"tercul/internal/domain"
	"tercul/internal/platform/config"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/trace"
	"gorm.io/gorm"
)

type placeRepository struct {
	domain.BaseRepository[domain.Place]
	db     *gorm.DB
	tracer trace.Tracer
}

// NewPlaceRepository creates a new PlaceRepository.
func NewPlaceRepository(db *gorm.DB, cfg *config.Config) domain.PlaceRepository {
	return &placeRepository{
		BaseRepository: NewBaseRepositoryImpl[domain.Place](db, cfg),
		db:             db,
		tracer:         otel.Tracer("place.repository"),
	}
}

// ListByCountryID finds places by country ID
func (r *placeRepository) ListByCountryID(ctx context.Context, countryID uint) ([]domain.Place, error) {
	ctx, span := r.tracer.Start(ctx, "ListByCountryID")
	defer span.End()
	var places []domain.Place
	if err := r.db.WithContext(ctx).Where("country_id = ?", countryID).Find(&places).Error; err != nil {
		return nil, err
	}
	return places, nil
}

// ListByCityID finds places by city ID
func (r *placeRepository) ListByCityID(ctx context.Context, cityID uint) ([]domain.Place, error) {
	ctx, span := r.tracer.Start(ctx, "ListByCityID")
	defer span.End()
	var places []domain.Place
	if err := r.db.WithContext(ctx).Where("city_id = ?", cityID).Find(&places).Error; err != nil {
		return nil, err
	}
	return places, nil
}

// FindNearby finds places within a certain radius (in kilometers) of a point
func (r *placeRepository) FindNearby(ctx context.Context, latitude, longitude float64, radiusKm float64) ([]domain.Place, error) {
	ctx, span := r.tracer.Start(ctx, "FindNearby")
	defer span.End()
	// This is a simplified implementation that would need to be replaced with
	// a proper geospatial query based on the database being used
	var places []domain.Place

	// For PostgreSQL with PostGIS, you might use something like:
	// query := `SELECT * FROM places
	//          WHERE ST_DWithin(
	//              ST_MakePoint(longitude, latitude)::geography,
	//              ST_MakePoint(?, ?)::geography,
	//              ? * 1000)`
	// if err := r.db.WithContext(ctx).Raw(query, longitude, latitude, radiusKm).Scan(&places).Error; err != nil {
	//     return nil, err
	// }

	// For a simple approximation without geospatial extensions:
	// This is not accurate for large distances or near the poles
	latDelta := radiusKm / 111.0                                        // Approx. 111km per degree of latitude
	lonDelta := radiusKm / (111.0 * math.Cos(latitude*(math.Pi/180.0))) // Adjust for longitude

	if err := r.db.WithContext(ctx).Where("latitude BETWEEN ? AND ? AND longitude BETWEEN ? AND ?",
		latitude-latDelta, latitude+latDelta,
		longitude-lonDelta, longitude+lonDelta).
		Find(&places).Error; err != nil {
		return nil, err
	}

	return places, nil
}
