package mongos

import (
	"context"
	"errors"
	"github.com/Pena-Co-Ltd/amocrm_templategen_back/dal/model"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"go.uber.org/zap"
	"time"
)

type Amo struct {
	coll   *mongo.Collection
	logger *zap.Logger
}

func InitAmo(db *mongo.Database, logger *zap.Logger) *Amo {
	return &Amo{coll: db.Collection("amo"), logger: logger}
}

func (d *Amo) InsertOrUpdate(ctx context.Context, record *model.Amo) (string, error) {
	if record.UserID == "" {
		err := errors.New("got empty user id")
		d.logger.Error("ErrorDeleteAmo", zap.Error(err))
		return "", err
	}

	record.CreatedAt = time.Now()
	record.UpdatedAt = time.Now()

	found, err := d.GetByAccountID(ctx, record.UserID)
	if err != nil {
		return "", err
	}

	if found == nil {
		result, err := d.coll.InsertOne(ctx, record)
		if err != nil {
			d.logger.Error("ErrorInsertOrUpdateAmo", zap.Error(err))
			return "", err
		}

		return result.InsertedID.(primitive.ObjectID).Hex(), nil
	}

	record.CreatedAt = found.CreatedAt

	err = d.Update(ctx, record)
	if err != nil {
		d.logger.Error("ErrorInsertOrUpdateAmo", zap.Error(err))
		return "", err
	}

	return found.ID, nil
}

func (d *Amo) GetByID(ctx context.Context, id string) (*model.Amo, error) {
	objID, err := primitive.ObjectIDFromHex(id)

	if err != nil {
		d.logger.Error("ErrorGetAmo", zap.Error(err))
		return nil, err
	}

	filter := bson.M{"_id": objID, "is_deleted": false}

	var result model.Amo

	err = d.coll.FindOne(ctx, filter).Decode(&result)

	if err == mongo.ErrNoDocuments {
		return nil, nil
	} else {
		if err != nil {
			d.logger.Error("ErrorGetAmo", zap.Error(err))
			return nil, err
		}
	}

	d.logger.Info("InfoGetAmo", zap.String("id", result.ID))
	return &result, nil
}

func (d *Amo) GetListByUserID(ctx context.Context, userID string) ([]model.Amo, error) {
	filter := bson.M{"user_id": userID, "is_deleted": false}

	var result []model.Amo

	cur, err := d.coll.Find(ctx, filter)

	if err == mongo.ErrNoDocuments {
		return nil, nil
	} else {
		if err != nil {
			d.logger.Error("ErrorGetListAmo", zap.Error(err))
			return nil, err
		}
	}

	err = cur.All(ctx, &result)
	if err != nil {
		d.logger.Error("ErrorGetListAmo", zap.Error(err))
		return nil, err
	}

	d.logger.Info("InfoGetListAmo")
	return result, nil
}

func (d *Amo) GetByAccountID(ctx context.Context, accId string) (*model.Amo, error) {
	filter := bson.M{"account_id": accId, "is_deleted": false}

	var result model.Amo

	err := d.coll.FindOne(ctx, filter).Decode(&result)

	if err == mongo.ErrNoDocuments {
		return nil, nil
	} else {
		if err != nil {
			d.logger.Error("ErrorGetAmo", zap.Error(err))
			return nil, err
		}
	}

	d.logger.Info("InfoGetAmo", zap.String("id", result.ID))
	return &result, nil
}

// Update - обновляет запись по id или accountID
func (d *Amo) Update(ctx context.Context, record *model.Amo) error {
	filter := bson.M{"is_deleted": false}

	if record.ID != "" {
		objID, err := primitive.ObjectIDFromHex(record.ID)
		if err != nil {
			d.logger.Error("ErrorUpdateAmo", zap.Error(err))
			return err
		}

		filter["_id"] = objID
	} else if record.AccountID != "" {
		filter["account_id"] = record.UserID
	} else {
		err := errors.New("got empty id and account id")
		d.logger.Error("ErrorUpdateAmo", zap.Error(err))
		return err
	}

	update := bson.M{"updated_at": time.Now()}

	// Token
	if record.AccessToken != "" {
		update["access_token"] = record.AccessToken
	}

	if record.RefreshToken != "" {
		update["refresh_token"] = record.RefreshToken
	}

	if !record.ExpiresIn.IsZero() {
		update["expires_in"] = record.ExpiresIn
	}

	if record.TokenType != "" {
		update["token_type"] = record.TokenType
	}

	// AccessRules
	if record.AccessRules.Visibility != nil || len(record.AccessRules.Visibility) > 0 {
		update["access_rules.visibility"] = record.AccessRules.Visibility
	}

	if record.AccessRules.Creation != nil || len(record.AccessRules.Creation) > 0 {
		update["access_rules.creation"] = record.AccessRules.Creation
	}

	if record.AccessRules.Delete != nil || len(record.AccessRules.Delete) > 0 {
		update["access_rules.delete"] = record.AccessRules.Delete
	}

	_, err := d.coll.UpdateOne(ctx, filter, bson.D{{"$set", update}})

	if err != nil {
		d.logger.Error("ErrorUpdateAmo", zap.Error(err))
		return err
	}

	return nil
}

func (d *Amo) DeleteByUserID(ctx context.Context, userID string) error {
	if userID == "" {
		err := errors.New("got empty user id")
		d.logger.Error("ErrorDeleteAmo", zap.Error(err))
		return err
	}

	filter := bson.M{"user_id": userID, "is_deleted": false}

	update := bson.M{"updated_at": time.Now(), "is_deleted": true}

	_, err := d.coll.UpdateMany(ctx, filter, bson.D{{"$set", update}})
	if err != nil {
		d.logger.Error("ErrorDeleteAmo", zap.Error(err))
		return err
	}

	return nil
}

func (d *Amo) UpdateAccessRules(ctx context.Context, id string, record *model.AmoAccessRules) error {
	objID, err := primitive.ObjectIDFromHex(id)

	if err != nil {
		d.logger.Error("ErrorUpdateAmoAccessRules", zap.Error(err))
		return err
	}

	filter := bson.M{"_id": objID, "is_deleted": false}

	update := bson.M{"updated_at": time.Now()}

	if record.Visibility != nil || len(record.Visibility) > 0 {
		update["access_rules.visibility"] = record.Visibility
	}

	if record.Creation != nil || len(record.Creation) > 0 {
		update["access_rules.creation"] = record.Creation
	}

	if record.Delete != nil || len(record.Delete) > 0 {
		update["access_rules.delete"] = record.Delete
	}

	_, err = d.coll.UpdateOne(ctx, filter, bson.D{{"$set", update}})

	if err != nil {
		d.logger.Error("ErrorUpdateAmoAccessRules", zap.Error(err))
		return err
	}

	return nil
}
