package config

import (
	"fmt"
	"os"
	"time"

	validation "github.com/Thearas/ozzo-validation/v4"
	"gopkg.in/yaml.v3"

	"github.com/pingcap/test-infra/sdk/pkg/tidbcloud"
	"github.com/pingcap/test-infra/sdk/resource/impl/k8s"
)

var (
	DefaultRMSConfiguration = RMSConfiguration{Provider: "k8s",
		K8s:                  K8sProviderConfiguration{AuthorizeRole: "cluster-admin", AuthorizeRoleForDebugger: "view"},
		TCMS:                 TCMSConfiguration{URL: "https://tcms.pingcap.net"},
		EnsureAuthenticated:  true,
		ReclaimConfiguration: ReclaimConfiguration{Interval: "30m"},
		ResourceQuota:        ResourceQuota{TestbedPerUser: 1},
	}
)

func ParseConfig(configData []byte) error {
	if len(configData) == 0 {
		return nil
	}

	if err := yaml.Unmarshal(configData, &DefaultRMSConfiguration); err != nil {
		return fmt.Errorf("parse config file error: %s\n%s", err, string(configData))
	}
	DefaultRMSConfiguration.MinIO.ID = os.Getenv("MINIO_ID")
	DefaultRMSConfiguration.MinIO.Secret = os.Getenv("MINIO_SECRET")

	DefaultRMSConfiguration.TiDBCloudConfiguration = tidbcloud.TiDBCloudConfiguration{
		TiDBCloudURL:             os.Getenv("TIDBCLOUD_URL"),
		ThanosURL:                os.Getenv("TIDBCLOUD_THANOS_URL"),
		Auth0ClientID:            os.Getenv("TIDBCLOUD_AUTH0_CLIENT_ID"),
		Auth0ClientSecret:        os.Getenv("TIDBCLOUD_AUTH0_CLIENT_SECRET"),
		Auth0Domain:              os.Getenv("TIDBCLOUD_AUTH0_DOMAIN"),
		CloudProvider:            os.Getenv("TIDBCLOUD_CLOUD_PROVIDER"),
		Region:                   os.Getenv("TIDBCLOUD_REGION"),
		BaseEKSAPIServerEndpoint: os.Getenv("TIDBCLOUD_BASE_EKS_APISERVER_ENDPOINT"),
		BaseEKSAssumeRole:        os.Getenv("TIDBCLOUD_BASE_EKS_ASSUME_ROLE"),
		BaseEKSClusterName:       os.Getenv("TIDBCLOUD_BASE_EKS_CLUSTER_NAME"),
		ShootKubeConfigSecretNS:  os.Getenv("TIDBCLOUD_SHOOT_KUBE_CONFIG_SECRET_NS"),
	}

	if err := DefaultRMSConfiguration.Validate(); err != nil {
		return fmt.Errorf("validate config file error: %s", err)
	}

	return nil
}

type RMSConfiguration struct {
	Provider               string                           `yaml:"provider"`
	TCMS                   TCMSConfiguration                `yaml:"tcms"`
	JwtPubKey              string                           `yaml:"jwtPubKey"`
	ReclaimConfiguration   ReclaimConfiguration             `yaml:"reclaim"`
	K8s                    K8sProviderConfiguration         `yaml:"k8s"`
	RMSURL                 string                           `yaml:"rmsURL"`
	DiagServerURL          string                           `yaml:"diagServerURL"`
	StatisticsService      StatisticsServiceConfiguration   `yaml:"statisticsService"`
	ResourceQuota          ResourceQuota                    `yaml:"quota"`
	EnsureAuthenticated    bool                             `yaml:"ensureAuthenticated"`
	Proxy                  ProxyConfig                      `yaml:"proxy"`
	MinIO                  MinIO                            `yaml:"minio"`
	TiDBCloudConfiguration tidbcloud.TiDBCloudConfiguration `yaml:"-"`
	AwsEcrRoleArn          string                           `yaml:"awsEcrRoleArn"`
}

type TCMSConfiguration struct {
	URL                    string `yaml:"url"`
	PlanExecutionURLFormat string `yaml:"planExecutionURLFormat"`
}

func (t TCMSConfiguration) Validate() error {
	return validation.ValidateStruct(&t,
		validation.Field(&t.URL, validation.Required),
		validation.Field(&t.PlanExecutionURLFormat, validation.Required))
}

type MinIO struct {
	URL               string `yaml:"url"`
	ConsoleBrowserURL string `yaml:"consoleBrowserURL"`
	ID                string `yaml:"-"`
	Secret            string `yaml:"-"`
}

func (r RMSConfiguration) Validate() error {
	if r.Provider != "k8s" {
		return fmt.Errorf("provider must be one of [k8s]")
	}
	return validation.ValidateStruct(&r,
		validation.Field(&r.Provider),
		validation.Field(&r.K8s),
		validation.Field(&r.TCMS),
		validation.Field(&r.StatisticsService),
		validation.Field(&r.JwtPubKey),
		validation.Field(&r.EnsureAuthenticated),
		validation.Field(&r.ReclaimConfiguration),
		validation.Field(&r.ResourceQuota),
		validation.Field(&r.TiDBCloudConfiguration),
	)
}

type StatisticsServiceConfiguration struct {
	PrometheusURL             string `yaml:"prometheusURL"`
	StaticResourcePoolMetrics struct {
		UseStaticMetrics bool    `yaml:"useStaticMetrics"`
		CPUCores         float64 `yaml:"cpuCores"`
		MemoryBytes      int     `yaml:"memoryBytes"`
	} `yaml:"staticResourcePoolMetrics"`
}

func (r StatisticsServiceConfiguration) Validate() error {
	if r.StaticResourcePoolMetrics.UseStaticMetrics {
		if r.StaticResourcePoolMetrics.CPUCores <= 0 || r.StaticResourcePoolMetrics.MemoryBytes <= 0 {
			return fmt.Errorf("expect valid static metrics setup, got %#v", r.StaticResourcePoolMetrics)
		}
	}

	return validation.ValidateStruct(&r,
		validation.Field(&r.PrometheusURL, validation.Required))
}

type ResourceQuota struct {
	TestbedPerUser int64 `yaml:"testbedPerUser"`
}

func (r ResourceQuota) Validate() error {
	if r.TestbedPerUser < 0 {
		return fmt.Errorf("the field testbed cannot be negative number")
	}
	return nil
}

type ReclaimConfiguration struct {
	Interval string `yaml:"interval"`
	// NoReserveEnvWhenNotInWorkingHour is a config switch. When it's on and while the TPS completes a testbed when not in working hour time,
	// the testbed won't be reserve and the testbed reclaim cron job will reclaim it quickly to release the resources occupies.
	// It's useful when exist too many cases haven't stable
	NoReserveEnvWhenNotInWorkingHour bool `yaml:"noReserveEnvWhenNotInWorkingHour"`
}

func (r ReclaimConfiguration) Validate() error {
	if _, err := time.ParseDuration(r.Interval); err != nil {
		return fmt.Errorf("parse interval error: %v", err)
	}
	return validation.ValidateStruct(&r,
		validation.Field(&r.Interval))
}

type K8sProviderConfiguration struct {
	SDKKubeConfig            string `yaml:"sdkKubeConfig"`
	AuthorizeRole            string `yaml:"authorizeRole"`
	AuthorizeRoleForDebugger string `yaml:"authorizeRoleForDebugger"`
	// CSRSignerName is CertificateSigningRequest signer name, on idc it can be `kubernetes.io/kube-apiserver-client`, but
	// on EKS, it should be `beta.eks.amazonaws.com/app-serving`: https://github.com/aws/containers-roadmap/issues/1604#issuecomment-1089585937
	CSRSignerName string `yaml:"csrSignerName"`
	K8sApiserver  string `yaml:"k8sApiserver"`
	k8s.Config    `yaml:",inline"`

	// token bucket ratelimit setting to k8s.io/client-go
	RequestBurst int     `yaml:"requestBurst"`
	RequestQPS   float32 `yaml:"requestQPS"`

	KubeSchedulerName string `yaml:"kubeSchedulerName"`
}

func (k K8sProviderConfiguration) Validate() error {
	if k.RequestBurst <= 0 || k.RequestQPS <= 0 {
		return fmt.Errorf("requestBurst and requestQPS shouldn't be negative")
	}
	return validation.ValidateStruct(&k,
		validation.Field(&k.SDKKubeConfig),
		validation.Field(&k.AuthorizeRole, validation.Required),
		validation.Field(&k.AuthorizeRoleForDebugger, validation.Required),
		validation.Field(&k.CSRSignerName, validation.Required),
		validation.Field(&k.K8sApiserver, validation.Required),
		validation.Field(&k.Config),
	)
}

type ProxyConfig struct {
	HTTPProxy  string `json:"HTTP_PROXY,omitempty" yaml:"HTTP_PROXY"`
	MySQLProxy string `json:"MYSQL_PROXY,omitempty" yaml:"MYSQL_PROXY"`
	GRPCProxy  string `json:"GRPC_PROXY,omitempty" yaml:"GRPC_PROXY"`
}
