package gormutil

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"time"

	"github.com/go-sql-driver/mysql"
	"github.com/tidbcloud/account/commons/config"
)

// Config represents the configuration which can be converted to a connection string
type Config struct {
	User                     string `valid:"required"`
	Host                     string `valid:"required"`
	Database                 string `valid:"required"`
	Password                 string
	Port                     uint16
	EnableDetailedLog        bool
	LogLevel                 string
	CACert                   string
	ClientCert               string
	ClientKey                string
	TLSConfigKey             string
	ConnMaxLifetime          time.Duration
	MaxIdleConns             int
	MaxOpenConns             int
	DisableNestedTransaction bool
}

func NewConfigFromMysql(c config.MysqlConfig) Config {
	return Config{
		User:     c.User,
		Host:     c.Host,
		Port:     c.Port,
		Database: c.Database,
		Password: c.Password,
	}
}

func (c Config) toMySQLConfigWithDefaults() *mysql.Config {
	defaultConfig := mysql.NewConfig()

	defaultConfig.Net = "tcp"
	defaultConfig.User = c.User
	defaultConfig.Addr = fmt.Sprintf("%s:%d", c.Host, c.Port)
	defaultConfig.Passwd = c.Password
	defaultConfig.DBName = c.Database
	defaultConfig.ParseTime = true
	defaultConfig.Timeout = 30 * time.Second
	defaultConfig.ReadTimeout = 30 * time.Second
	defaultConfig.WriteTimeout = 30 * time.Second
	defaultConfig.Params = map[string]string{"charset": "utf8mb4"}
	defaultConfig.Collation = "utf8mb4_bin"
	defaultConfig.TLSConfig = c.TLSConfigKey

	return defaultConfig
}

func (c Config) IsMutualTLSEnabled() bool {
	return len(c.TLSConfigKey) > 0 && len(c.CACert) > 0 && len(c.ClientCert) > 0 && len(c.ClientKey) > 0
}

func (c Config) RegisterMutualTLSToDriver() error {
	if !c.IsMutualTLSEnabled() {
		return fmt.Errorf("failed to enable tls: some of tls configs (ca, client key, or client cert) are missing")
	}
	// init ca
	rootCertPool := x509.NewCertPool()
	if ok := rootCertPool.AppendCertsFromPEM([]byte(c.CACert)); !ok {
		return fmt.Errorf("parsing and appending certificates failed")
	}
	// init client cert and client key
	clientCert := make([]tls.Certificate, 0, 1)
	certs, err := tls.X509KeyPair([]byte(c.ClientCert), []byte(c.ClientKey))
	if err != nil {
		return err
	}
	clientCert = append(clientCert, certs)
	// register ca and cert to mysql driver
	return mysql.RegisterTLSConfig(c.TLSConfigKey, &tls.Config{
		MinVersion:   tls.VersionTLS12,
		RootCAs:      rootCertPool,
		Certificates: clientCert,
	})
}

// DSN gives a connection string suitable to go mysql driver
func (c Config) DSN() string {
	return c.toMySQLConfigWithDefaults().FormatDSN()
}

func (c Config) DSNWithTestDB() string {
	localConfig := c.toMySQLConfigWithDefaults()
	// Fixme: Is forcing "test" database necessary when init db driver?
	localConfig.DBName = "test"
	return localConfig.FormatDSN()
}

// DSNWithoutPassword implements the fmt.Stringer interface.
func (c Config) DSNWithoutPassword() string {
	mysqlConfig := c.toMySQLConfigWithDefaults()
	mysqlConfig.Passwd = ""
	return mysqlConfig.FormatDSN()
}
