// Tool Url: https://github.com/jinzhu/configor

package config

import (
	"fmt"
	"os"
	"strings"

	"dataservices/commons/constants"

	"github.com/tidbcloud/tiinf/tiinf"
)

const (
	O11YRegionUrl  = "o11YRegionUrl"
	O11YCentralUrl = "o11YCentralUrl"
)

// Database configuration
type CentralConfig struct {
	TiInfConf               tiinf.TiInfConfig              `mapstructure:"tiinf"`
	ServerConf              ServerConfig                   `mapstructure:"server"`
	SentryConf              SentryConfig                   `mapstructure:"sentry"`
	DBHubSdkConf            DBHubSdkConfig                 `mapstructure:"dbhubsdk"`
	MysqlConf               MysqlConfig                    `mapstructure:"mysql"`
	GrpcConf                GrpcConfig                     `mapstructure:"grpc"`
	DataApiKeyConf          DataApiKeyConfig               `mapstructure:"dataapikey"`
	RBACConf                RBACConfig                     `mapstructure:"rbacConfig"`
	AwsConf                 AwsConfig                      `mapstructure:"awsConfig"`
	VpcConf                 VpcConfig                      `mapstructure:"vpcConfig"`
	RegionalConf            RegionalConfig                 `mapstructure:"regionalConfig"`
	GhappConf               GhappConfig                    `mapstructure:"ghapp"`
	SkuCon                  MeasureSkuConfig               `mapstructure:"measure"`
	DataappConf             DataappConfig                  `mapstructure:"dataapp"`
	RedisConf               RedisConfig                    `mapstructure:"redis"`
	LogQueryRedisConf       RedisConfig                    `mapstructure:"logQueryRedis"`
	ServerlessConf          ServerlessConfig               `mapstructure:"serverless"`
	O11YRegionMetricUrl     string                         `mapstructure:"o11YRegionMetricUrl"`
	EndpointConf            EndpointConfig                 `mapstructure:"endpointConfig"`
	CustomLogWriteConf      CustomLogWriteConfig           `mapstructure:"CustomLogWriteConf"`
	CustomLogQueryConf      CustomLogQueryConfig           `mapstructure:"CustomLogQueryConf"`
	DataAppMetricsQueryConf DataAppMetricsQueryConfig      `mapstructure:"DataAppMetricsQueryConf"`
	DataAppMetricsInstrConf DataAppMetricsInstrumentConfig `mapstructure:"DataAppMetricsInstrConf"`
}

func (c *CentralConfig) IsRegionMode() bool {
	return !c.IsCentralMode()
}
func (c *CentralConfig) IsCentralMode() bool {
	return c.ServerConf.DeployMode == "central"
}
func (c *CentralConfig) IsAliCloudRegionalMode() bool {
	return c.IsRegionMode() && strings.EqualFold(c.ServerlessConf.Provider, constants.ClusterCloudProviderAliCloud)
}
func (c *CentralConfig) CurrentEnv() string {
	return c.ServerConf.Env
}
func (c *CentralConfig) IsProd() bool {
	return c.CurrentEnv() == "prod"
}
func (c *CentralConfig) IsDev() bool {
	return c.CurrentEnv() == "dev"
}
func (c *CentralConfig) IsStaging() bool {
	return c.CurrentEnv() == "staging"
}

type SecretConfig struct {
	SecretKey string `mapstructure:"secretKey"`
	Region    string `mapstructure:"region"`
}

type EndpointConfig struct {
	EnableSession bool `mapstructure:"enableSession" default:"false"`
}

type DataappConfig struct {
	JwtSecretKey string `mapstructure:"jwtSecretKey"`
}

type DataApiKeyConfig struct {
	SecretKey                 string `mapstructure:"SecretKey"`
	SecretIv                  string `mapstructure:"SecretIv"`
	GatewayRedisMasterAddress string `mapstructure:"GatewayRedisMasterAddress"`
	GatewayRedisPassword      string `mapstructure:"GatewayRedisPassword"`
}

type ServerConfig struct {
	Env              string        `mapstructure:"env" default:"prod"`
	LogLevel         string        `mapstructure:"logLevel" default:"info"`
	GrpcPort         string        `mapstructure:"grpcPort" default:"10000"`
	HttpPort         string        `mapstructure:"httpPort" default:"10010"`
	MetricsPort      string        `mapstructure:"metricsPort" default:"9090"`
	LocalMode        bool          `mapstructure:"localMode" default:"false"`
	CrossRegionToken string        `mapstructure:"crossRegionToken"`
	DeployMode       string        `mapstructure:"deployMode"`
	SecretStore      *SecretConfig `mapstructure:"secret"`
}

type GrpcConfig struct {
	AccountProviderSvcAddr string `mapstructure:"accountProviderSvcAddr" default:"account-provider-grpc:10000"`
	AccountAclSvcAddr      string `mapstructure:"accountAclSvcAddr" default:"account-acl-grpc:10000"`
	RegioncentralSvcAddr   string `mapstructure:"regioncentralSvcAddr" default:"dataservices-regioncentral-service:10000"`
	ExecutorSvcAddr        string `mapstructure:"executorSvcAddr" default:"kubernetes:///dataservice-dataapp-executor-service:10000"`
	DataserviceGatewayAddr string `mapstructure:"dataserviceGatewayAddr" default:"dataservice-dataapp-gateway-service:10000"`
	AipowerAddr            string `mapstructure:"aipowerAddr" default:"aipower-api-service:10000"`
	SqleditorAddr          string `mapstructure:"sqleditorAddr" default:"sqleditor-http:10000"`
	CoreSvcAddr            string `mapstructure:"coreSvcAddr" default:"dbaas-core-svc-server-grpc:8888"`
}

type DBHubSdkConfig struct {
	GardenerProjectName string `mapstructure:"gardenerProjectName" default:"nightly-ms" required:"true"`
	HttpListenAddr      string `mapstructure:"httpListenAddr" default:":8080"`
	KubeConfigPath      string `mapstructure:"kubeConfigPath"`
	InfraAPIConfigPath  string `mapstructure:"infraAPIConfigPath"`
}

type SentryConfig struct {
	DSN         string `mapstructure:"dsn"`
	Environment string `mapstructure:"env"`
	LogLevel    string `mapstructure:"logLevel" default:"warn"`
}

type MysqlConfig struct {
	Host     string `mapstructure:"host"  required:"true"`
	User     string `mapstructure:"user" default:"dataservices"`
	Port     uint   `mapstructure:"port" default:"4000"`
	Password string `mapstructure:"password" required:"true"`
	Database string `mapstructure:"database" default:"dataservices"`
	CharSet  string `mapstructure:"charset"  default:"utf8mb4"`
	TimeZone string `mapstructure:"timezone"  default:"UTC"`
}

type RedisConfig struct {
	MasterAddr  string `mapstructure:"masterAddr" required:"true"`
	ReplicaAddr string `mapstructure:"replicaAddr" required:"true"`
	Password    string `mapstructure:"password" required:"true"`
	DB          int    `mapstructure:"db" required:"true"`
	Tls         bool   `mapstructure:"tls" required:"true"`
}

type ServerlessConfig struct {
	PrivatelinkEnabled bool   `mapstructure:"privatelinkEnable"`
	AwsVPCPrivateDNS   string `mapstructure:"awsVPCPrivateDNS"`
	Provider           string `mapstructure:"provider"`
	// Special project IDs that use gateway02 for serverless access
	ProjectIds02 []uint64              `mapstructure:"projectIds02"`
	StaticRoutes []ServerlessRouteItem `mapstructure:"staticRoutes"`
}

type ServerlessRouteItem struct {
	ProjectId uint64 `mapstructure:"projectId"`
	ClusterId uint64 `mapstructure:"clusterId"`
	Region    string `mapstructure:"region"`
	Cloud     string `mapstructure:"cloud"`
	Host      string `mapstructure:"host"`
	Port      int    `mapstructure:"port"`
	UseTLS    bool   `mapstructure:"useTls"`
}

func (c ServerlessConfig) FindStaticRoute(projectId, clusterId uint64) (*ServerlessRouteItem, bool) {
	for i := range c.StaticRoutes {
		route := &c.StaticRoutes[i]
		if route.ProjectId == projectId && route.ClusterId == clusterId {
			return route, true
		}
	}
	// AliCloud V2 typically runs one regional executor per serverless target. In
	// that case the static route can be configured with only host/port/useTls and
	// omit projectId/clusterId entirely.
	if len(c.StaticRoutes) == 1 {
		route := &c.StaticRoutes[0]
		if route.ProjectId == 0 && route.ClusterId == 0 {
			return route, true
		}
	}
	return nil, false
}

func (c ServerlessConfig) ValidateStaticRoutes() error {
	seen := make(map[[2]uint64]struct{}, len(c.StaticRoutes))
	for _, route := range c.StaticRoutes {
		if route.Host == "" || route.Port == 0 {
			return fmt.Errorf("invalid static route for project_id=%d cluster_id=%d", route.ProjectId, route.ClusterId)
		}
		if (route.ProjectId == 0) != (route.ClusterId == 0) {
			return fmt.Errorf("invalid static route selector for project_id=%d cluster_id=%d", route.ProjectId, route.ClusterId)
		}
		if route.ProjectId == 0 && route.ClusterId == 0 {
			if len(c.StaticRoutes) != 1 {
				return fmt.Errorf("default static route requires exactly one route entry")
			}
			continue
		}
		key := [2]uint64{route.ProjectId, route.ClusterId}
		if _, ok := seen[key]; ok {
			return fmt.Errorf("duplicated static route for project_id=%d cluster_id=%d", route.ProjectId, route.ClusterId)
		}
		seen[key] = struct{}{}
	}
	return nil
}

func (c *CentralConfig) ValidateAliCloudRegionalConfig() error {
	if !c.IsAliCloudRegionalMode() {
		return nil
	}
	if len(c.ServerlessConf.StaticRoutes) == 0 {
		return fmt.Errorf("serverless staticRoutes is empty in alicloud regional mode")
	}
	return c.ServerlessConf.ValidateStaticRoutes()
}

type GhappConfig struct {
	AppId                       int64  `mapstructure:"appId" required:"true"`
	WebhookSecret               string `mapstructure:"webhookSecret" required:"true"`
	ClientId                    string `mapstructure:"clientId" required:"true"`
	ClientSecret                string `mapstructure:"clientSecret" required:"true"`
	PrivateKeyFile              string `mapstructure:"privateKeyFile" required:"true"`
	PreflightRequestRedirectUrl string `mapstructure:"preflightRequestRedirectUrl" required:"true"`
	UpdateDataappRedirectUrl    string `mapstructure:"updateDataappRedirectUrl" required:"true"`
}

type RBACConfig struct {
	ApiKeyTableName        string `mapstructure:"apiKeyTableName"  default:"dev_account_user_api_keys"`
	PrivilegeRoleTableName string `mapstructure:"privilegeRoleTableName"  default:"dev_account_privilege_role"`
}

type AwsConfig struct {
	Region  string `mapstructure:"region"  default:"us-west-2"`
	Session string `mapstructure:"session"  default:"dev-role-session-name"`
	RoleArn string `mapstructure:"roleArn"  default:"arn:aws:iam::385595570414:role/dev-staging-data-api-dynamodb-role"`
}

type VpcConfig struct {
	VpcId           string            `mapstructure:"vpcId" required:"true"`
	SecurityGroupId string            `mapstructure:"securityGroupId" required:"true"`
	AzConfList      []AzConfig        `mapstructure:"azConfig" required:"true"`
	az2SubnetMap    map[string]string `mapstructure:"-"`
}

type RegionalConfig struct {
	GatewayRedirect     bool   `mapstructure:"gatewayRedirect" default:"false"`
	GatewayRedirectAddr string `mapstructure:"gatewayRedirectAddr" default:"data.tidbcloud.com"`
	SessionFailFast     bool   `mapstructure:"sessionFailFast" default:"false"`
	// DataserviceControlUnsupported controls which (provider, region) pairs do NOT
	// deploy dataservice-control/dataapp-control. Keys are cloud providers (e.g. "aws"),
	// values are region codes (e.g. "ap-east-1").
	DataserviceControlUnsupported map[string][]string `mapstructure:"dataserviceControlUnsupported"`
}

type CustomLogWriteConfig struct {
	LogDir     string `mapstructure:"logDir" default:"/var/log/dataservice-customlog"`
	FileName   string `mapstructure:"filename" default:"endpoint_exec.log"`
	MaxSize    int    `mapstructure:"fileMaxSize" default:"512"`
	MaxDays    int    `mapstructure:"maxDays" default:"2"`
	MaxBackups int    `mapstructure:"maxBackups" default:"4"`
}

type CustomLogQueryConfig struct {
	LokiAddr               string `mapstructure:"querierAddr" default:"loki-dataservice-read.dataservice-infra.svc.cluster.local"`
	LokiPort               int    `mapstructure:"querierPort" default:"3100"`
	QueryLokiTimeoutSec    int    `mapstructure:"QueryLokiTimeoutSec" default:"60"`
	CacheExpirySec         int    `mapstructure:"CacheExpirySec" default:"900"`
	CacheChunkSize         uint64 `mapstructure:"CacheChunkSize" default:"300"`
	MaxFetchChunkNum       uint64 `mapstructure:"MaxFetchChunkNum" default:"3"`
	MaxDownloadLines       int    `mapstructure:"MaxDownloadLines" default:"10000"`
	DownloadTempDir        string `mapstructure:"DownloadTempDir" default:"/var/log"`
	S3Bucket               string `mapstructure:"S3Bucket" default:"dataservice-dataapp-logdowload-dev"`
	DownloadFileExp        int    `mapstructure:"DownloadFileExp" default:"36000"`
	DownloadTaskTimeoutSec int    `mapstructure:"DownloadTaskTimeoutSec" default:"600"`
}

type DataAppMetricsQueryConfig struct {
	LokiAddr                   string `mapstructure:"LokiAddr" default:"loki-dataservice-read.dataservice-infra.svc.cluster.local"`
	LokiPort                   int    `mapstructure:"LokiPort" default:"3100"`
	PromAddr                   string `mapstructure:"PromAddr" default:"svc-dataservice-custom-metrics-prom.tidbcloud-dataservice-custom.svc.cluster.local"`
	PromPort                   int    `mapstructure:"PromPort" default:"9090"`
	UseLokiThresSec            int    `mapstructure:"UseLokiThresSec" default:"1800"`
	QueryPromTimeoutSec        int    `mapstructure:"QueryLokiTimeoutSec" default:"60"`
	HighResolutionThresholdSec int    `mapstructure:"HighResolutionThresholdSec" default:"300"`
}

type DataAppMetricsInstrumentConfig struct {
	UnauthMetricsFetchInterval int         `mapstructure:"UnauthMetricsFetchInterval" default:"3"`
	UnauthMetricsFetchKey      string      `mapstructure:"UnauthMetricsFetchKey" default:"gateway_unauth_report"`
	UnauthReportRds            RedisConfig `mapstructure:"UnauthReportRds"`
}

func (c *VpcConfig) GetAz2SubnetMap() map[string]string {
	if len(c.az2SubnetMap) > 0 {
		return c.az2SubnetMap
	}
	c.az2SubnetMap = make(map[string]string)
	for _, azConf := range c.AzConfList {
		c.az2SubnetMap[azConf.AzName] = azConf.SubnetId
	}
	return c.az2SubnetMap
}

type AzConfig struct {
	AzName   string `mapstructure:"azName"  required:"true"`
	SubnetId string `mapstructure:"subnetId"  required:"true"`
}

type MeasureSkuConfig struct {
	SkuRequest string `mapstructure:"skuRequest"`
	SkuNetWork string `mapstructure:"skuNetWork"`
}

var globalConf = &CentralConfig{}

func IsLocalMode() bool {
	return globalConf.ServerConf.LocalMode
}

func IsCentralMode() bool {
	return globalConf.IsCentralMode()
}

func IsRegionMode() bool {
	return globalConf.IsRegionMode()
}

func IsAliCloudRegionalMode() bool {
	return globalConf.IsAliCloudRegionalMode()
}

func CurrentEnv() string {
	return globalConf.CurrentEnv()
}
func IsProd() bool {
	return globalConf.IsProd()
}
func IsDev() bool {
	return globalConf.IsDev()
}
func IsStaging() bool {
	return globalConf.IsStaging()
}

// SupportsDataserviceControl reports whether dataservice-control/dataapp-control
// is deployed for the given provider & region.
//
// If provider is empty, it defaults to "aws" (the legacy regional host rule).
func SupportsDataserviceControl(provider, region string) bool {
	if strings.TrimSpace(region) == "" {
		return true
	}
	if strings.TrimSpace(provider) == "" {
		provider = constants.ClusterCloudProviderAWS
	}
	normalizedProvider := strings.ToLower(strings.TrimSpace(provider))

	unsupported := globalConf.RegionalConf.DataserviceControlUnsupported
	if regions, ok := unsupported[normalizedProvider]; ok {
		for _, r := range regions {
			if strings.EqualFold(strings.TrimSpace(r), region) {
				return false
			}
		}
	}
	return true
}
func RpcEndpointAddr() string {
	return os.Getenv("POD_IP") + ":" + globalConf.ServerConf.GrpcPort
}
