package mongodb

import (
	"context"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"golang.source-fellows.com/training-fellow/booking/internal"
	"golang.source-fellows.com/training-fellow/booking/internal/model"
	"golang.source-fellows.com/training-fellow/booking/internal/mongodb/filter"
	"log"
	"sync"
	"time"
)

//go:generate mongo-query-gen -in ../model/training.go -outDir .

const TrainingDatabaseName = "Booking"
const TrainingsCollectionName = "Trainings"

var documentCountLimit = int64(500)
var limitDocumentResultOption = &options.FindOptions{Limit: &documentCountLimit}

var initLock sync.Mutex

type Config struct {
	Username string `envconfig:"DB_USERNAME" required:"true"`
	Passwort string `envconfig:"DB_PASSWORD" required:"true"`
	Url      string `envconfig:"DB_URL" required:"true"`
}

type TrainingStore struct {
	config Config
	client *mongo.Client
}

func NewTrainingStorage(config Config) *TrainingStore {
	return &TrainingStore{config: config}
}

func (s *TrainingStore) InitClient(ctx context.Context) error {

	initLock.Lock()
	defer initLock.Unlock()
	if s.client != nil {
		return nil
	}

	credential := options.Credential{
		Username:      s.config.Username,
		Password:      s.config.Passwort,
		AuthMechanism: "SCRAM-SHA-256",
	}

	client, err := mongo.Connect(ctx, options.Client().
		ApplyURI(s.config.Url).
		SetAuth(credential),
		options.Client().SetConnectTimeout(5*time.Second),
		options.Client().SetTimeout(5*time.Second),
	)
	if err != nil {
		return wrapError(err)
	}

	s.client = client
	return nil

}

func (s *TrainingStore) FindTrainings(ctx context.Context) ([]*model.Training, error) {

	if err := s.InitClient(ctx); err != nil {
		return nil, wrapError(err)
	}

	collection := s.client.Database(TrainingDatabaseName).Collection(TrainingsCollectionName)

	//limit the result!
	cursor, err := collection.Find(ctx, filter.TrainingFilter{}, limitDocumentResultOption)
	if err != nil {
		return nil, wrapError(err)
	}

	var results []*model.Training
	for cursor.Next(ctx) {
		training := Training{}
		err := cursor.Decode(&training)
		if err != nil {
			return nil, wrapError(err)
		}
		results = append(results, mapToModelTraining(training))
	}

	if cursor.Err() != nil {
		return results, wrapError(cursor.Err())
	}

	return results, nil
}

func (s *TrainingStore) AddNewTraining(ctx context.Context, training *model.Training) error {

	log.Printf("%+v", training)
	log.Printf("%+v", mapToMongoTraining(training))

	if err := s.InitClient(ctx); err != nil {
		return wrapError(err)
	}

	collection := s.client.Database(TrainingDatabaseName).Collection(TrainingsCollectionName)
	_, err := collection.InsertOne(ctx, mapToMongoTraining(training))
	return wrapError(err)

}

func wrapError(err error) error {
	if mongo.IsTimeout(err) {
		return internal.NewUnavailableError(err)
	}
	return err
}
