package dbcore

import (
	"context"
	"fmt"
	"reflect"

	"github.com/milvus-io/milvus/internal/log"
	"github.com/milvus-io/milvus/internal/util/paramtable"
	"go.uber.org/zap"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var (
	globalDB *gorm.DB
)

func Connect(cfg *paramtable.MetaDBConfig) error {
	// load config
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		cfg.Username.GetValue(), cfg.Password.GetValue(), cfg.Address.GetValue(), cfg.Port.GetAsInt(), cfg.DBName.GetValue())

	var ormLogger logger.Interface
	if log.Level().String() == "debug" {
		ormLogger = logger.Default.LogMode(logger.Info)
	} else {
		ormLogger = logger.Default
	}

	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger:          ormLogger,
		CreateBatchSize: 100,
	})
	if err != nil {
		log.Error("fail to connect db",
			zap.String("host", cfg.Address.GetValue()),
			zap.Int("port", cfg.Port.GetAsInt()),
			zap.String("database", cfg.DBName.GetValue()),
			zap.Error(err))
		return err
	}

	idb, err := db.DB()
	if err != nil {
		log.Error("fail to create db instance",
			zap.String("host", cfg.Address.GetValue()),
			zap.Int("port", cfg.Port.GetAsInt()),
			zap.String("database", cfg.DBName.GetValue()),
			zap.Error(err))
		return err
	}
	idb.SetMaxIdleConns(cfg.MaxIdleConns.GetAsInt())
	idb.SetMaxOpenConns(cfg.MaxOpenConns.GetAsInt())

	globalDB = db

	log.Info("db connected success",
		zap.String("host", cfg.Address.GetValue()),
		zap.Int("port", cfg.Port.GetAsInt()),
		zap.String("database", cfg.DBName.GetValue()),
		zap.Error(err))

	return nil
}

// SetGlobalDB Only for test
func SetGlobalDB(db *gorm.DB) {
	globalDB = db
}

type ctxTransactionKey struct{}

func CtxWithTransaction(ctx context.Context, tx *gorm.DB) context.Context {
	if ctx == nil {
		ctx = context.Background()
	}
	return context.WithValue(ctx, ctxTransactionKey{}, tx)
}

type txImpl struct{}

func NewTxImpl() *txImpl {
	return &txImpl{}
}

func (*txImpl) Transaction(ctx context.Context, fn func(txctx context.Context) error) error {
	db := globalDB.WithContext(ctx)

	return db.Transaction(func(tx *gorm.DB) error {
		txCtx := CtxWithTransaction(ctx, tx)
		return fn(txCtx)
	})
}

func GetDB(ctx context.Context) *gorm.DB {
	iface := ctx.Value(ctxTransactionKey{})

	if iface != nil {
		tx, ok := iface.(*gorm.DB)
		if !ok {
			log.Error("unexpect context value type", zap.Any("type", reflect.TypeOf(tx)))
			return nil
		}

		return tx
	}

	return globalDB.WithContext(ctx)
}

//type CommonModel struct {
//	ID        string    `gorm:"primary_key"`
//	IsDeleted bool      `gorm:"is_deleted"`
//	CreatedAt time.Time `gorm:"created_at"`
//	UpdatedAt time.Time `gorm:"updated_at"`
//}
