package db

import (
	"context"
	"os"

	"github.com/grafana/grafana/pkg/util/xorm/core"

	"github.com/grafana/grafana/pkg/util/xorm"

	"github.com/grafana/grafana/pkg/services/sqlstore"
	"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
	"github.com/grafana/grafana/pkg/services/sqlstore/session"
	"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
	"github.com/grafana/grafana/pkg/setting"
)

type DB interface {
	// WithTransactionalDbSession creates a new SQL transaction to ensure consistency
	// for the database operations done within the [sqlstore.DBTransactionFunc].
	// It's better to combine InTransaction and WithDbSession instead, as the context
	// variable is not updated when using this method.
	WithTransactionalDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
	// WithDbSession runs database operations either in an existing transaction available
	// through [context.Context] or if that's not present, as non-transactional database
	// operations.
	WithDbSession(ctx context.Context, callback sqlstore.DBTransactionFunc) error
	// GetDialect returns an object that contains information about the peculiarities of
	// the particular database type available to the runtime.
	GetDialect() migrator.Dialect
	// GetDBType returns the name of the database type available to the runtime.
	GetDBType() core.DbType
	// GetEngine returns the underlying xorm engine.
	GetEngine() *xorm.Engine
	// GetSqlxSession is an experimental extension to use sqlx instead of xorm to
	// communicate with the database.
	// NOTE: when using this session with mysql, the connection will *not* have:
	// the expected parameters: "&sql_mode='ANSI_QUOTES" and "&parseTime=true"
	// The sqlx session is useful, but be careful not to expect automagic date parsing
	GetSqlxSession() *session.SessionDB
	// InTransaction creates a new SQL transaction that is placed on the context.
	// Use together with [DB.WithDbSession] to run database operations.
	InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
	// Quote wraps an identifier so that it cannot be mistaken for an SQL keyword.
	Quote(value string) string
	// RecursiveQueriesAreSupported runs a dummy recursive query and it returns true
	// if the query runs successfully or false if it fails with mysqlerr.ER_PARSE_ERROR error or any other error
	RecursiveQueriesAreSupported() (bool, error)
}

type Session = sqlstore.DBSession
type InitTestDBOpt = sqlstore.InitTestDBOpt

var SetupTestDB = sqlstore.SetupTestDB
var CleanupTestDB = sqlstore.CleanupTestDB
var ProvideService = sqlstore.ProvideService

func InitTestDB(t sqlutil.ITestDB, opts ...InitTestDBOpt) *sqlstore.SQLStore {
	db, _ := InitTestDBWithCfg(t, opts...)
	return db
}

func InitTestDBWithCfg(t sqlutil.ITestDB, opts ...InitTestDBOpt) (*sqlstore.SQLStore, *setting.Cfg) {
	return sqlstore.InitTestDB(t, opts...)
}

func IsTestDbSQLite() bool {
	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); !present || db == "sqlite" {
		return true
	}

	return !IsTestDbMySQL() && !IsTestDbPostgres()
}

func IsTestDbMySQL() bool {
	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
		return db == migrator.MySQL
	}

	return false
}

func IsTestDbPostgres() bool {
	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
		return db == migrator.Postgres
	}

	return false
}

func IsTestDBMSSQL() bool {
	if db, present := os.LookupEnv("GRAFANA_TEST_DB"); present {
		return db == migrator.MSSQL
	}

	return false
}
