package storage

import (
	"sync/atomic"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/pkgv2/gormutil"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

var (
	globalDB *gorm.DB
	// initialized prevents the database connection pool being initialized multiple
	// times. An error will be returned to the caller if the database has been initialized.
	initialized atomic.Bool
)

func Global() *gorm.DB {
	return globalDB
}

type RdsConfig struct {
	Host     string `json:"host"`
	Port     uint16 `json:"port"`
	UserName string `json:"username"`
	Password string `json:"password"`
	Dbname   string `json:"dbname"`
}

func (r *RdsConfig) Validate() error {
	if r.Host == "" {
		return errors.New("host is empty")
	}
	if r.UserName == "" {
		return errors.New("username is empty")
	}
	if r.Password == "" {
		return errors.New("password is empty")
	}
	if r.Dbname == "" {
		return errors.New("dbname is empty")
	}
	if r.Port == 0 {
		r.Port = 3306
	}

	return nil
}

// ToGormutilConfig converts RdsConfig to gormutil.Config.
func (r *RdsConfig) ToGormutilConfig() *gormutil.Config {
	connCfg := &gormutil.Config{
		User:     r.UserName,
		Host:     r.Host,
		Database: r.Dbname,
		Password: r.Password,
		Port:     r.Port,
	}

	switch log.GetLevel() {
	case zap.DebugLevel:
		connCfg.LogLevel = "info"
		connCfg.EnableDetailedLog = true
	case zap.InfoLevel:
		connCfg.LogLevel = "info"
	case zap.WarnLevel:
		connCfg.LogLevel = "warn"
	case zap.ErrorLevel:
		connCfg.LogLevel = "error"
	case zap.FatalLevel:
		connCfg.LogLevel = "fatal"
	default:
		connCfg.LogLevel = "info"
	}

	return connCfg
}

func NewGormDB(conf *RdsConfig) (*gorm.DB, error) {
	if initialized.Swap(true) {
		return globalDB, nil
	}

	if err := conf.Validate(); err != nil {
		return nil, errors.Trace(err)
	}

	connCfg := conf.ToGormutilConfig()

	db, err := gormutil.Connect(*connCfg)
	if err != nil {
		return nil, errors.Trace(err)
	}
	if err := db.Use(&MetricsPlugin{}); err != nil {
		return nil, errors.Trace(err)
	}
	globalDB = db
	return db, nil
}

func NewDBProvier(gormDB *gorm.DB) (db.Provider, db.TransProvider) {
	provider := db.NewProvider(gormDB)
	return provider, provider
}
