package db

import (
	"database/sql"
	"fmt"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"github.com/tidbcloud/config-center/internal/config"
)

type DB interface {
	Begin() (DB, error)
	Rollback() error
	Commit() error

	CreateTemplateRow(templateID *int64, clusterType, filename, templateName, tags, contentHash string) (int64, error)
	GetTemplateRow(id int64) (TemplateRow, error)
	ListTemplatesByHash(clusterType, configType, contentHash string) ([]TemplateRow, error)
	// ListTemplateRows does not apply pagination when pageSize <= 0.
	ListTemplateRows(clusterType, configType string, tagFilter map[string]string, templateName string, pageSize, page int32) ([]TemplateRow, error)
	CountTemplateRows(clusterType, configType string, tagFilter map[string]string, templateName string) (int64, error)
	DeleteTemplateRow(id int64) error

	InsertTemplateValueRows(rows []TemplateValueRow) error
	ListTemplateValueRows(templateID int64) ([]TemplateValueRow, error)
	DeleteTemplateValueRows(templateID int64) error

	GetDefaultTemplate(clusterType, configType, version, cloud, instance, region, tenantID string) (int64, error)
	CountDefaultTemplateByTemplateID(templateID int64) (int64, error)
	ListDefaultTemplateByTemplate(clusterType, configType string, templateID int64) ([]DefaultTemplateRow, error)
	ListDefaultTemplateBindings(clusterType string, configType, version, cloud, instance, tenantID string, pageSize, pageNum int32, exactMatch bool) ([]DefaultTemplateRow, error)
	CountDefaultTemplateBindings(clusterType string, configType, version, cloud, instance, tenantID string, exactMatch bool) (int64, error)
	InsertDefaultTemplate(clusterType, configType, version, cloud, instance, region, tenantID string, templateID int64) error
	UpdateDefaultTemplate(clusterType, configType, version, cloud, instance, region, tenantID string, templateID int64) error
	DeleteDefaultTemplate(clusterType, configType, version, cloud, instance, region, tenantID string) error
	ListAvailableInstancesFromDefaultTemplate(clusterType string, configType, version *string) ([]UniqueInstance, error)

	InsertTenantInitialCustomParams(clusterType, ConfigType string, tenantID uint64, items []ConfigurationItem) error
	ListTenantInitialCustomParams(clusterType, configType string, tenantID uint64) ([]TenantInitialCustomParamRow, error)
	ListAllTenantInitialCustomParams(clusterType string) ([]TenantInitialCustomParamRow, error)
	ListTenantInitialConfigTypes(clusterType string, tenantID uint64) ([]string, error)
	DeleteTenantInitialCustomParams(clusterType, ConfigType string, tenantID uint64) error

	InsertClusterConfigInfo(row ClusterConfigInfoRow) (int64, error)
	GetClusterConfigInfo(clusterType, clusterID, filename string) (ClusterConfigInfoRow, error)
	ListClusterConfigInfo(clusterType, clusterID string) ([]ClusterConfigInfoRow, error)
	ListClusterConfigInfo2(clusterType, clusterID, configType, relatedComponent string) ([]ClusterConfigInfoRow, error)
	CountClusterConfigInfoByTemplateID(templateID int64) (int64, error)
	ListClusterConfigInfoByTemplate(clusterType, filename string, templateID int64) ([]ClusterConfigInfoRow, error)
	UpdateClusterConfigInfo(row ClusterConfigInfoRow) error
	DeleteClusterConfigInfoByFile(clusterType, clusterID, filename string) error
	DeleteClusterConfigInfoByCluster(clusterType, clusterID string) (int64, error)

	ListClusterConfigParams(clusterType, clusterID, filename string) ([]ClusterConfigParamRow, error)
	InsertClusterConfigParams(clusterType, clusterID, filename string, items []ConfigurationItem) error
	UpsertClusterConfigParams(clusterType, clusterID, filename string, items []ConfigurationItem) error
	DeleteClusterConfigParamsByFile(clusterType, clusterID, filename string) error
	DeleteClusterConfigParamsByCluster(clusterType, clusterID string) error

	CreateClusterConfigText(clusterType, clusterID, filename, text string) error
	GetClusterConfigText(clusterType, clusterID, filename string) (text string, err error)
	UpdateClusterConfigText(clusterType, clusterID, filename string, text string) error
	DeleteClusterConfigText(clusterType, clusterID, filename string) error

	InsertDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64, version string) error
	GetDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64) (string, error)
	ListDefaultVersionRules(clusterType string, component *string, tenantID *uint64) ([]DefaultVersionRow, error)
	UpdateDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64, version string) error
	DeleteDefaultVersion(clusterType, component, clusterVersion, cloudProvider, region string, teanantID uint64) error

	InsertComponentImage(clusterType, component, version string, baseImage, tag string, tenantID uint64) error
	GetComponentImage(clusterType, component, version string, tenantID uint64) (string, string, error)
	ListComponentImages(clusterType, component, version string) ([]ComponentImageRow, error)
	UpdateComponentImage(clusterType, component, version string, baseImage, tag string, tenantID uint64) error
	UpsertComponentImage(clusterType, component, version, baseImage, tag string, tenantID uint64) error
	DeleteComponentImage(clusterType, component, version string) error
	ListVersionsFromComponentImage(clusterType, component string) ([]string, error)

	InsertClusterComponent(cluster_type, cluster_id, component_name, version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag string, update_strategy int) error
	GetClusterComponent(clusterType, clusterID, componentName string) (ClusterComponentRow, error)
	ListClusterComponents(clusterType, clusterID string) ([]ClusterComponentRow, error)
	SearchClusterComponents(clusterType, componentName string, clusterIDs []string, componentVersion, imageTag string, showDeleted bool, pageSize, page int32) ([]ClusterComponentRow, error)
	CountClusterComponents(clusterType, componentName string, clusterIDs []string, componentVersion, imageTag string, showDeleted bool) (int64, error)
	UpdateClusterComponent(cluster_type, cluster_id, component_name, version, cloud_provider, instance, overwrite_image_tag, base_image, image_tag string, update_strategy int) error
	DeleteClusterComponentsByCluster(clusterType, clusterID string) (int64, error)

	GetConfigDict(clusterType, configType, paramKey, version string) (*ConfigDictRow, error)
	ListConfigDict(opt *ListConfigDictOption) ([]*ConfigDictRow, error)
	ListVersionsFromConfigDict(clusterType, configType string) ([]string, error)
	InsertConfigDicts(items []*ConfigDictRow) error
	UpdateConfigDict(item *ConfigDictRow) error
	GetTotalParamNum(opt *ListConfigDictOption) (int64, error)
	DeleteDictItems(clusterType, configType string, version *string) (int64, error)

	InsertAuditLog(api_name, cluster_type, cluster_id string, request_ts_ms, commit_ts_ms int64, request, response string) error
	UpsertRegionalService(clusterType, logicalRegion, endpoint string) error
	DeleteRegionalService(clusterType, logicalRegion string) error
	ListRegionalServices() ([]*RegionRoute, error)
	GetRegionalService(clusterType, logicalRegion string) (*RegionRoute, error)
	UpdateRegionalServiceSyncStatus(clusterType, logicalRegion, syncStatus string) error
}

// sqlx.DB or sqlx.TX
type dbtx interface {
	//Rollback() error
	//Commit() error
	Exec(query string, args ...any) (sql.Result, error)
	NamedExec(query string, arg interface{}) (sql.Result, error)
	Get(dest interface{}, query string, args ...interface{}) error
	Select(dest interface{}, query string, args ...interface{}) error
	Rebind(query string) string
	Query(query string, args ...any) (*sql.Rows, error)
}

type realDB struct {
	dbtx
	ignoreCommit bool
}

func NewRealDB(c config.DatabaseConfig) DB {
	var err error
	db, err = sqlx.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=UTC", c.User, c.Password, c.Host, c.Port, c.Database))
	if err != nil {
		panic(err)
	}
	if err = db.Ping(); err != nil {
		panic(err)
	}
	return &realDB{dbtx: db}
}

// If run Begin() on a transaction, it will return a new transaction that will ignore Commit() and Rollback()
func (db *realDB) Begin() (DB, error) {
	if dbx, ok := db.dbtx.(*sqlx.DB); ok {
		tx, err := dbx.Beginx()
		if err != nil {
			return nil, err
		}
		return &realDB{dbtx: tx}, nil
	} else if tx, ok := db.dbtx.(*sqlx.Tx); ok {
		return &realDB{
			dbtx:         tx,
			ignoreCommit: true,
		}, nil
	}

	panic(fmt.Sprintf("unknown db type %T", db.dbtx))
}

func (db *realDB) Rollback() error {
	if tx, ok := db.dbtx.(*sqlx.Tx); ok && !db.ignoreCommit {
		return tx.Rollback()
	}

	return nil
}

func (db *realDB) Commit() error {
	if tx, ok := db.dbtx.(*sqlx.Tx); ok && !db.ignoreCommit {
		return tx.Commit()
	}

	return nil
}
