package mysql

import (
	"fmt"
	"sync"

	"gorm.io/gorm"

	genericoptions "finance-portal/internal/pkg/option"

	"finance-portal/internal/dataplatform/store"
	"finance-portal/internal/pkg/code"
	"finance-portal/internal/pkg/logger"
	"finance-portal/pkg/db"
	"finance-portal/pkg/errors"
)

type datastore struct {
	db *gorm.DB
}

func (ds *datastore) WebView() store.WebViewStore {
	return newWebView(ds)
}

func (ds *datastore) CloudOrder() store.CloudOrderStore {
	return newCloudOrder(ds)
}

func (ds *datastore) CloudRevrec() store.CloudRevrecStore {
	return newCloudRevrec(ds)
}

func (ds *datastore) CloudCollection() store.CloudCollectionStore {
	return newCloudCollection(ds)
}

func (ds *datastore) BqAccount() store.BqAccountStore {
	return newBqAccount(ds)
}

func (ds *datastore) BqOrder() store.BqOrderStore {
	return newBqOrder(ds)
}

func (ds *datastore) AuditLog() store.AuditLogStore {
	return newAuditLog(ds)
}

func (ds *datastore) Salesforce() store.SalesforceStore {
	return newSalesforce(ds)
}

func (ds *datastore) Common() store.CommonStore {
	return newCommon(ds)
}

func (ds *datastore) ARTable() store.ARTableStore {
	return newARTable(ds)
}

func (ds *datastore) MarketplaceCollection() store.MarketplaceCollectionStore {
	return newMarketplaceCollection(ds)
}

func (ds *datastore) PublicCollection() store.PublicCollectionStore {
	return newPublicCollection(ds)
}

func (ds *datastore) InvoiceRewrite() store.InvoiceRewriteStore {
	return newInvoiceRewrite(ds)
}

func (ds *datastore) NSRevRec() store.NSRevrecStore {
	return newNSRevrec(ds)
}

func (ds *datastore) ExamService() store.ExamServiceStore {
	return newExamService(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
}

// NewStoreFactoryFromDB creates a store.Factory from an existing gorm.DB instance.
func NewStoreFactoryFromDB(db *gorm.DB) store.Factory {
	return &datastore{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,
			MaxIdleConnections:    opts.MaxIdleConnections,
			MaxOpenConnections:    opts.MaxOpenConnections,
			MaxConnectionLifeTime: opts.MaxConnectionLifeTime,
			LogLevel:              opts.LogLevel,
			Logger:                logger.New(opts.LogLevel),
			Tls:                   opts.Tls,
		}
		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
}

func dbNotFoundErrorWrap(er error) error {
	if er != nil {
		if errors.Is(er, gorm.ErrRecordNotFound) {
			return errors.WithCode(code.ErrRecordNotFound, "gorm.ErrRecordNotFound: %v", er.Error())
		}

		return errors.WithCode(code.ErrDatabase, "gorm.ErrRecordNotFound: %v", er.Error())
	}

	return nil
}
