package database

import (
	"context"
	"database/sql"
	"fmt"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/commons/config"
	"github.com/tidbcloud/account/pkg/gormutil"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/atomic"
	"gorm.io/gorm"
)

var DBConn = &MySqlInfo{}

type MySqlInfo struct {
	DB *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.
var initialized atomic.Bool

// for test only.
func ResetInitialize() {
	initialized.Swap(false)
}

// Initialize initializes the global database connection pool and return error
// if some unexpected errors from the driver and gorm package raised.
func Initialize(ctx context.Context, cfg config.MysqlConfig) (*gorm.DB, error) {
	if initialized.Swap(true) {
		return nil, errors.New("global database is already initialized")
	}

	user := cfg.User
	password := cfg.Password
	if cfg.SecondUser != "" && cfg.SecondUserPassword != "" {
		tilog.Info(context.Background(), fmt.Sprintf("Initialize global database use second user: %s", cfg.SecondUser))
		user = cfg.SecondUser
		password = cfg.SecondUserPassword
	}

	connCfg := gormutil.Config{
		User:                     user,
		Host:                     cfg.Host,
		Database:                 cfg.Database,
		Password:                 password,
		Port:                     cfg.Port,
		DisableNestedTransaction: cfg.DisableNestedTransaction,
		EnableDetailedLog:        config.Global().ServerConfig.EnableDBQueryLog,
		LogLevel:                 config.Global().ServerConfig.LogLevel,
	}
	if err := gormutil.EnsureConnectionDatabase(connCfg); err != nil {
		return nil, errors.Trace(err)
	}
	gormDB, err := gormutil.Connect(connCfg)
	if err != nil {
		return nil, errors.Trace(err)
	}
	DBConn.DB = gormDB

	tilog.Info(ctx, "Initialize global database successfully")

	return gormDB, nil
}

// Transaction starts a transaction as a block, return error will rollback, otherwise to commit.
func Transaction(fc func(tx *gorm.DB) error, opts ...*sql.TxOptions) (err error) {
	ctx, cancel := context.WithTimeout(context.Background(), config.DefaultDBOperationTimeout)
	defer cancel()
	defer func() {
		if r := recover(); r != nil {
			err = errors.Errorf("%v", r)
		}
	}()
	err = DBConn.DB.WithContext(ctx).Transaction(fc, opts...)
	return
}

// Create inserts a new record into the database.
func Create(obj interface{}) error {
	return Transaction(func(tx *gorm.DB) error { return tx.Create(obj).Error })
}

func Delete(value interface{}, conds ...interface{}) error {
	return Transaction(func(tx *gorm.DB) error {
		return tx.Delete(value, conds...).Error
	})
}
