package gormutil

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

	"github.com/go-sql-driver/mysql"
)

// TODO(chenfei): remove pkg/gormutil

const (
	defaultTimeout      = 60 * time.Second
	defaultReadTimeout  = 30 * time.Second
	defaultWriteTimeout = 30 * time.Second
)

// Config represents the configuration which can be converted to a connection string
type Config struct {
	User              string `yaml:"user" default:"root" valid:"required"`
	Host              string `yaml:"host" default:"host" valid:"required"`
	Database          string `yaml:"database" default:"dbaas" valid:"required"`
	Password          string `yaml:"password" required:"true"`
	Port              uint16 `yaml:"port" default:"4000"`
	EnableDetailedLog bool   `yaml:"enableDBQueryLog" default:"false"`
	LogLevel          string `yaml:"logLevel" default:"info"`
	CACert            string
	ClientCert        string
	ClientKey         string
	TLSConfigKey      string
	// Timeout controls the timeout for establishing a new connection.
	// Non-positive values use the default 60-second timeout.
	Timeout time.Duration `yaml:"timeout"`
	// ReadTimeout controls the timeout for database reads.
	// Non-positive values use the default 30-second timeout.
	ReadTimeout time.Duration `yaml:"readTimeout"`
	// WriteTimeout controls the timeout for database writes.
	// Non-positive values use the default 30-second timeout.
	WriteTimeout    time.Duration `yaml:"writeTimeout"`
	ConnMaxLifetime time.Duration
	MaxIdleConns    int
	MaxOpenConns    int
}

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 = positiveDurationOrDefault(c.Timeout, defaultTimeout)
	defaultConfig.ReadTimeout = positiveDurationOrDefault(c.ReadTimeout, defaultReadTimeout)
	defaultConfig.WriteTimeout = positiveDurationOrDefault(c.WriteTimeout, defaultWriteTimeout)
	defaultConfig.Params = map[string]string{"charset": "utf8mb4"}
	defaultConfig.Collation = "utf8mb4_bin"
	defaultConfig.TLSConfig = c.TLSConfigKey

	return defaultConfig
}

func positiveDurationOrDefault(value, defaultValue time.Duration) time.Duration {
	if value > 0 {
		return value
	}
	return defaultValue
}

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()
}
