package config

import (
	"fmt"
	"net"
	"os"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/secretsmanager"
	"github.com/imdario/mergo"
	"github.com/pingcap/errors"
	"gopkg.in/yaml.v3"

	"github.com/pingcap-inc/clinic/pkg/config"
)

const (
	DefaultAddress = ":12021"

	// Service name
	TopSQL       = "topsql"
	TopRu        = "topru"
	ConProf      = "conprof"
	SlowQuery    = "slow_query"
	Statements   = "statements"
	OOMRecord    = "oom_record"
	PlanReplayer = "plan_replayer"
)

type NgmApiServerConfig struct {
	Address string `yaml:"address"`
	Env     string `yaml:"env"`

	CacheDir string `yaml:"cache-dir"`

	Tracing Tracing `yaml:"tracing"`

	// Available services:
	// - topsql
	// - topru
	// - conprof
	// - slow_query
	// - statements
	// - oom_record
	// - plan_replayer
	// - performance_insight
	DisableServices []string `yaml:"disable-services"`

	// For slow_query, statements, oom_record, plan_replayer
	S3AssumeRoleArn string `yaml:"s3-assume-role-arn"`
	Region          string `yaml:"region"`

	// For performance_insight
	DBConfig                *config.DBConfig          `yaml:"db"`
	DBaasConfig             *config.DBaasConfig       `yaml:"dbaas-config"`
	OutgoingNatAddresses    []string                  `yaml:"outgoing-nat-addresses"`
	AwsSecretsManagerRegion string                    `yaml:"aws-secrets-manager-region"`
	ClinicSecretKey         string                    `yaml:"clinic-secret-key"`
	ComputeEngineAddress    string                    `yaml:"compute-engine-address"`
	TenantDBConfig          *config.DBConfig          `yaml:"tenant-db-config"`
	CentralO11yConfig       *config.CentralO11yConfig `yaml:"central-o11y"`
}

func (c *NgmApiServerConfig) IsRegion() bool {
	return strings.HasPrefix(c.Region, "cn-")
}

type Tracing struct {
	// Deprecated: NGM no longer exports spans to Jaeger. Trace header
	// propagation and log correlation remain enabled independently.
	Enable bool `yaml:"enable"`
}

func (c *NgmApiServerConfig) validate() error {
	if len(c.OutgoingNatAddresses) <= 0 {
		return fmt.Errorf("config.OutgoingNatAddresses cannot be empty")
	}
	for _, addr := range c.OutgoingNatAddresses {
		if net.ParseIP(addr) == nil {
			return fmt.Errorf("config.OutgoingNatAddresses should be ip address")
		}
	}
	if c.Env == "" {
		return fmt.Errorf("config.Env cannot be empty")
	}
	return nil
}

func InitConfig(cfgPath string) (*NgmApiServerConfig, error) {
	b, err := os.ReadFile(cfgPath)
	if err != nil {
		return nil, err
	}
	var cfg NgmApiServerConfig
	if err := yaml.Unmarshal(b, &cfg); err != nil {
		return nil, err
	}
	if cfg.Address == "" {
		cfg.Address = DefaultAddress
	}
	if err := mergeDBConfigFromClinicSecrets(&cfg); err != nil {
		return nil, err
	}
	if err := cfg.validate(); err != nil {
		return nil, err
	}
	return &cfg, nil
}

func (c *NgmApiServerConfig) IsServiceEnabled(service string) bool {
	for _, s := range c.DisableServices {
		if s == service {
			return false
		}
	}
	return true
}

// Get clinic server secrets.
func getSecretFromAwsSecretManager(secretName, region string) (string, error) {
	awsConfig := aws.NewConfig().WithMaxRetries(3).WithRegion(region)
	opts := session.Options{Config: *awsConfig}
	sess, err := session.NewSessionWithOptions(opts)
	if err != nil {
		return "", err
	}
	svc := secretsmanager.New(sess)
	input := &secretsmanager.GetSecretValueInput{
		SecretId:     aws.String(secretName),
		VersionStage: aws.String("AWSCURRENT"),
	}
	result, err := svc.GetSecretValue(input)
	if err != nil {
		return "", err
	}
	return *result.SecretString, nil
}

func mergeDBConfigFromClinicSecrets(cfg *NgmApiServerConfig) error {
	// If the secret key is not set, we will not merge the secrets.
	if cfg.ClinicSecretKey == "" {
		return nil
	}

	secretConfig, err := getSecretFromAwsSecretManager(cfg.ClinicSecretKey, cfg.AwsSecretsManagerRegion)
	if err != nil {
		return errors.Annotate(err, "failed to get secrets from aws secret manager")
	}
	var cfgInAws NgmApiServerConfig
	if err := yaml.Unmarshal([]byte(secretConfig), &cfgInAws); err != nil {
		return errors.Annotate(err, "failed to unmarshal secrets")
	}

	// Only merge the DBConfig.
	if err := mergo.Merge(cfg.DBConfig, cfgInAws.DBConfig, mergo.WithOverride); err != nil {
		return errors.Annotate(err, "failed to merge db config from secrets")
	}

	if err := mergo.Merge(cfg.TenantDBConfig, cfgInAws.TenantDBConfig, mergo.WithOverride); err != nil {
		return errors.Annotate(err, "failed to merge tenant db config from secrets")
	}

	if err := mergo.Merge(cfg.CentralO11yConfig, cfgInAws.CentralO11yConfig, mergo.WithOverride); err != nil {
		return errors.Annotate(err, "failed to merge tenant db config from secrets")
	}

	return nil
}
