package config

import (
	"fmt"
	"os"
	"time"

	"github.com/Shopify/sarama"
	"github.com/go-sql-driver/mysql"
	"github.com/imdario/mergo"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/eventbus-datastruct/event"
	"gopkg.in/yaml.v3"
)

type Config struct {
	DBConfig       *DBConfig      `yaml:"db"`
	Activityhub    Activityhub    `yaml:"activity_hub"`
	DBaasConfig    DBaasConfig    `yaml:"dbaas"`
	EventConfig    EventConfig    `yaml:"event"`
	AlertConfig    AlertConfig    `yaml:"alert"`
	JiralertConfig JiralertConfig `yaml:"jiralert"`
	O11YConfig     O11YConfig     `yaml:"o11y_config"`

	// for switch deployment, disable event handler when switching not finished
	DisableInNewDeployment bool `yaml:"disable-in-new-deployment"`

	// KafkaExt configures KafkaExt SASL/TLS for tiinf auditlog / tieventbus when audit_kafka.sasl.enable is true.
	// Broker addresses still come from OneConfig (kafka_brokers); this only sets transport auth.
	KafkaExt KafkaExtConfig `yaml:"kafka_ext"`
}

// KafkaExtConfig is used by cloud_server/pkg/auditloginit for Sarama SASL/TLS.
type KafkaExtConfig struct {
	SASL KafkaSASL `yaml:"sasl"`
	TLS  KafkaTLS  `yaml:"tls"`
}

// KafkaSASL enables PLAIN or SCRAM-* when Enable is true.
type KafkaSASL struct {
	Enabled   bool                 `yaml:"enabled"`
	Mechanism sarama.SASLMechanism `yaml:"mechanism"` // PLAIN, SCRAM-SHA-256, SCRAM-SHA-512
	Username  string               `yaml:"username"`
	Password  string               `yaml:"password"`
}

// KafkaTLS is applied when tls.enable is true or sasl.enable is true (TLS defaults on with SASL).
type KafkaTLS struct {
	Enabled            bool `yaml:"enabled"`
	InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
}

func (c *Config) Validate() error {
	if c.DBConfig == nil {
		return fmt.Errorf("db config is required")
	}

	if c.Activityhub.Endpoint == "" {
		return fmt.Errorf("activity hub endpoint is required")
	}

	if c.Activityhub.Source == "" {
		return fmt.Errorf("activity hub source is required")
	}

	if c.Activityhub.Key == "" {
		return fmt.Errorf("activity hub key is required")
	}

	if c.DBaasConfig.AccountEndpoint == "" {
		return fmt.Errorf("account endpoint is required")
	}

	if c.DBaasConfig.CoresvcEndpoint == "" {
		return fmt.Errorf("coresvc endpoint is required")
	}

	return nil
}

func InitConfig(cfgPath, secretConfigPath string) (*Config, error) {
	cfg := Config{}
	bytes, err := os.ReadFile(cfgPath)
	if err != nil {
		return nil, errors.Annotate(err, "failed to read config")
	}

	err = yaml.Unmarshal(bytes, &cfg)
	if err != nil {
		return nil, errors.Annotate(err, "failed to read unmarshal config")
	}

	if secretConfigPath != "" {
		secretCfg := Config{}
		bytes, err := os.ReadFile(secretConfigPath)
		if err != nil {
			return nil, errors.Annotate(err, "failed to read secret config")
		}

		err = yaml.Unmarshal(bytes, &secretCfg)
		if err != nil {
			return nil, errors.Annotate(err, "failed to unmarshal secret config")
		}

		if err = mergo.Merge(&cfg, &secretCfg, mergo.WithOverride); err != nil {
			return nil, errors.Annotate(err, "failed to merge secret config")
		}
	}

	return &cfg, nil
}

type DBConfig struct {
	User     string `yaml:"user"`
	Host     string `yaml:"host"`
	Database string `yaml:"database"`
	// InitDatabase is used to connect to the very first created database.
	InitDatabase      string `yaml:"init-database"`
	Password          string `yaml:"password"`
	Port              uint16 `yaml:"port"`
	EnableDetailedLog bool   `yaml:"enable-detailed-log"`
	LogLevel          string `yaml:"log-level"`
	ConnMaxLifetime   time.Duration
	MaxIdleConns      int
	MaxOpenConns      int
}

type Activityhub struct {
	Endpoint string `yaml:"endpoint"`
	Source   string `yaml:"source"`
	Key      string `yaml:"key"`
}

type DBaasConfig struct {
	AccountEndpoint string `yaml:"account_endpoint"`
	CoresvcEndpoint string `yaml:"coresvc_endpoint"`
	BillingEndpoint string `yaml:"billing_endpoint"`
	MgmtSvcEndpoint string `yaml:"mgmt_svc_endpoint"`
}

func (c DBConfig) DSN() string {
	return c.toMySQLConfigWithDefaults().FormatDSN()
}

func (c DBConfig) DSNWithTestDB() string {
	localConfig := c.toMySQLConfigWithDefaults()

	initDatabase := c.InitDatabase
	if initDatabase == "" {
		initDatabase = "test"
	}
	localConfig.DBName = initDatabase
	return localConfig.FormatDSN()
}

func (c DBConfig) 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.Loc = time.UTC
	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.AllowNativePasswords = true

	return defaultConfig
}

type EventConfig struct {
	DedicatedTypeWhiteList  []string            `yaml:"dedicated_type_white_list"`
	ServerlessTypeWhiteList []string            `yaml:"serverless_type_white_list"`
	NextGenTypeWhiteList    []string            `yaml:"nextgen_type_white_list"`
	StatusWhiteList         []event.EventStatus `yaml:"status_white_list"`
}

type AlertConfig struct {
	OldDatabaseName     string `yaml:"old_database_name"`
	EnableScheduledSync bool   `yaml:"enable_scheduled_sync"`
	ScheduledSyncSecond int    `yaml:"scheduled_sync_second"`
}

type JiralertConfig struct {
	Endpoint string   `yaml:"endpoint"`
	Receiver Receiver `yaml:"receiver"`
}

type O11YConfig struct {
	GlobalServiceEndpoint string `yaml:"global_service_endpoint"`
	Auth0ClientID         string `yaml:"auth0_client_id"`
	Auth0ClientSecret     string `yaml:"auth0_client_secret"`
}

type Receiver struct {
	Warning  string `yaml:"warning"`
	Major    string `yaml:"major"`
	Critical string `yaml:"critical"`
}
