package driver

import (
	"context"
	"errors"
	"local/dndex/config"
	"log"
	"time"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
)

type Mongo struct {
	client *mongo.Client
	db     string
}

func NewMongo() Mongo {
	opts := options.Client().ApplyURI(config.New().DBURI)
	c, err := mongo.NewClient(opts)
	if err != nil {
		panic(err)
	}
	if err := c.Connect(context.TODO()); err != nil {
		panic(err)
	}
	ctx, can := context.WithTimeout(context.Background(), time.Second)
	defer can()
	if _, err := c.ListDatabaseNames(ctx, map[string]interface{}{}); err != nil {
		panic(err)
	}
	return Mongo{
		client: c,
		db:     config.New().Database,
	}
}

func (m Mongo) Find(ctx context.Context, namespace string, filter interface{}) (chan bson.Raw, error) {
	c := m.client.Database(m.db).Collection(namespace)
	cursor, err := c.Find(ctx, filter)
	if err != nil {
		return nil, err
	}
	return m.page(ctx, cursor), nil
}

func (m Mongo) page(ctx context.Context, cursor *mongo.Cursor) chan bson.Raw {
	ch := make(chan bson.Raw)
	go func(chan<- bson.Raw) {
		defer close(ch)
		defer cursor.Close(ctx)
		for cursor.Next(ctx) {
			ch <- cursor.Current
		}
	}(ch)
	return ch
}

func (m Mongo) Update(ctx context.Context, namespace string, filter, apply interface{}) error {
	c := m.client.Database(m.db).Collection(namespace)
	_, err := c.UpdateMany(ctx, filter, apply)
	return err
}

func (m Mongo) Insert(ctx context.Context, namespace string, apply interface{}) error {
	b, err := bson.Marshal(apply)
	if err != nil {
		return err
	}
	var mapp map[string]interface{}
	if err := bson.Unmarshal(b, &mapp); err != nil {
		return err
	}
	if _, ok := mapp["_id"]; !ok {
		return errors.New("no _id in new object")
	} else if _, ok := mapp["_id"].(string); !ok {
		return errors.New("non-string _id in new object")
	}
	c := m.client.Database(m.db).Collection(namespace)
	_, err = c.InsertOne(ctx, apply)
	return err
}

func (m Mongo) Delete(ctx context.Context, namespace string, filter interface{}) error {
	c := m.client.Database(m.db).Collection(namespace)
	_, err := c.DeleteMany(ctx, filter)
	return err
}

func peekBSON(v interface{}) {
	b, err := bson.Marshal(v)
	if err != nil {
		panic(err)
	}
	var m map[string]interface{}
	if err := bson.Unmarshal(b, &m); err != nil {
		panic(err)
	}
	log.Printf("PEEK: %+v", m)
}
