package mysql

import (
	"fmt"
	"sync"

	"github.com/spf13/viper"
	"gorm.io/gorm"

	"github.com/pingcap-inc/security/internal/pkg/logger"

	"github.com/pingcap-inc/security/internal/alert-server/store"
	genericoptions "github.com/pingcap-inc/security/internal/pkg/option"

	"github.com/pingcap-inc/pchub/pkg/db"
	"github.com/pingcap-inc/pchub/pkg/errors"
)

type datastore struct {
	db *gorm.DB
}

func (ds *datastore) User() store.UserStore {
	return newUser(ds)
}

func (ds *datastore) Alert() store.AlertStore {
	return newAlert(ds)
}

func (ds *datastore) DownLoadAlert() store.DownloadAlertStore {
	return newDownloadAlert(ds)
}

func (ds *datastore) FeilianAlert() store.FeilianAlertStore {
	return newFeilianAlert(ds)
}

func (ds *datastore) GmailLock() store.GmailLockStore {
	return newGmailLock(ds)
}

func (ds *datastore) Wiki() store.WikiStore {
	return newWiki(ds)
}

func (ds *datastore) Company() store.CompanyStore {
	return newCompany(ds)
}

func (ds *datastore) Close() error {
	dbins, err := ds.db.DB()
	if err != nil {
		return errors.Wrap(err, "get gorm db instance failed")
	}

	return dbins.Close()
}

func (ds *datastore) GetDB() *gorm.DB {
	return ds.db
}

var (
	mysqlFactory store.Factory
	once         sync.Once
)

// GetMySQLFactoryOr create mysql factory with the given config.
func GetMySQLFactoryOr(opts *genericoptions.MySQLOptions) (store.Factory, error) {
	if opts == nil && mysqlFactory == nil {
		return nil, fmt.Errorf("failed to get mysql store fatory")
	}

	var err error
	var dbIns *gorm.DB
	once.Do(func() {
		options := &db.Options{
			Host:                  opts.Host,
			Username:              opts.Username,
			Password:              opts.Password,
			Database:              opts.Database,
			UseUnixSocket:         opts.UseUnixSocket,
			MaxIdleConnections:    opts.MaxIdleConnections,
			MaxOpenConnections:    opts.MaxOpenConnections,
			MaxConnectionLifeTime: opts.MaxConnectionLifeTime,
			LogLevel:              opts.LogLevel,
			Logger:                logger.New(opts.LogLevel),
		}
		dbIns, err = db.New(options)
		mysqlFactory = &datastore{dbIns}
	})

	if mysqlFactory == nil || err != nil {
		return nil, fmt.Errorf("failed to get mysql store fatory, mysqlFactory: %+v, error: %w", mysqlFactory, err)
	}

	return mysqlFactory, nil
}

var (
	mysqlFactory2 store.Factory
	once2         sync.Once
)

func GetMySQLFactoryOr2() (store.Factory, error) {
	var err error
	var dbIns *gorm.DB
	once2.Do(func() {
		options := &db.Options{
			Host:     viper.GetString("mysql.host"),
			Username: viper.GetString("mysql.username-sub"),
			Password: viper.GetString("mysql.password-sub"),
			Database: viper.GetString("mysql.database-sub"),
		}
		dbIns, err = db.New(options)
		mysqlFactory2 = &datastore{dbIns}
	})

	if mysqlFactory2 == nil || err != nil {
		return nil, fmt.Errorf("failed to get mysql store fatory, mysqlFactory: %+v, error: %w", mysqlFactory, err)
	}

	return mysqlFactory2, nil
}
