package options

import (
	"fmt"
	"net"
	"net/url"
	"os"
	"time"

	"github.com/spf13/pflag"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/tidbcloud/shared-provider/cmd/manager/app/config"
)

// Options is flags of manager.
type Options struct {
	Kubeconfig      string
	InfraKubeconfig string

	// TODO: extract common options
	MetricsBindAddress     string
	WebhookPort            int
	HealthProbeBindAddress string
	EnableLeaderElection   bool

	OIDCProviderARN         string
	ConfigServiceUrl        string
	BaseProxy               string
	PublicHostedZoneDomain  string
	PublicHostedZoneId      string
	AddonConfigMapNamespace string
	AddonConfigMapName      string
	DNSPodSelector          string
	DNSNamespace            string
	KubeClusterName         string
	RemoteWriteUrl          string
	TiDBAccessCIDR          string
	EvictPodDuration        time.Duration

	LoadBalancerClusterName string
	LoadBalancerCIDRs       []string

	// aws
	AWSAccountID  string
	AWSRegion     string
	ImportRoleARN string

	// gcp
	GCPProjectID string
}

// New returns default running options
// nolint: gomnd // all constants are default config
func New() *Options {
	opt := &Options{
		Kubeconfig:              "",
		InfraKubeconfig:         "",
		MetricsBindAddress:      ":8080",
		WebhookPort:             9443,
		HealthProbeBindAddress:  ":8081",
		EnableLeaderElection:    false,
		AddonConfigMapNamespace: os.Getenv("NAMESPACE"),
		AddonConfigMapName:      "shared-provider-addon-template",

		DNSPodSelector:  "k8s-app=kube-dns",
		DNSNamespace:    "kube-system",
		KubeClusterName: "",

		AWSRegion: os.Getenv("AWS_REGION"),
	}

	return opt
}

// AddFlags adds flags for manager.
func (opt *Options) AddFlags(fs *pflag.FlagSet) {
	fs.StringVar(&opt.Kubeconfig, "kubeconfig", opt.Kubeconfig,
		"kubeconfig for user cluster",
	)
	fs.StringVar(&opt.InfraKubeconfig, "infra-kubeconfig", opt.InfraKubeconfig,
		"kubeconfig for cluster hosted infra api",
	)

	fs.StringVar(&opt.MetricsBindAddress, "metrics-bind-address", opt.MetricsBindAddress,
		"The address the metric endpoint binds to.",
	)
	fs.StringVar(&opt.HealthProbeBindAddress, "health-probe-bind-address", opt.HealthProbeBindAddress,
		"The address the probe endpoint binds to.",
	)
	fs.IntVar(&opt.WebhookPort, "webhook-port", opt.WebhookPort,
		"The port of manager webhook.",
	)
	fs.BoolVar(&opt.EnableLeaderElection, "enable-leader-election", opt.EnableLeaderElection,
		"Enable leader election for controller manager. "+
			"Enabling this will ensure there is only one active controller manager.",
	)

	fs.StringVar(&opt.AddonConfigMapNamespace, "addon-config-map-namespace", opt.AddonConfigMapNamespace,
		"Configmap namespace of addon template",
	)
	fs.StringVar(&opt.AddonConfigMapName, "addon-config-map-name", opt.AddonConfigMapName,
		"Configmap name of addon template",
	)

	fs.StringVar(&opt.DNSPodSelector, "dns-pod-selector", opt.DNSPodSelector,
		"Pod selector to select dns for network egress policy",
	)
	fs.StringVar(&opt.DNSNamespace, "dns-namespace", opt.DNSNamespace,
		"Namespace of dns for network egress policy",
	)

	fs.StringVar(&opt.OIDCProviderARN, "oidc-provider-arn", opt.OIDCProviderARN,
		"The OIDC Provider ARN.")
	fs.StringVar(&opt.ConfigServiceUrl, "config-service-url", opt.ConfigServiceUrl,
		"The address the config service url binds to.",
	)
	fs.StringVar(&opt.BaseProxy, "base-proxy", opt.BaseProxy,
		"The address the proxy binds to.",
	)
	fs.StringVar(&opt.PublicHostedZoneDomain, "public-hosted-zone-domain", opt.PublicHostedZoneDomain,
		"The zone domain the Route 53 binds to.",
	)
	fs.StringVar(&opt.PublicHostedZoneId, "public-hosted-zone-id", opt.PublicHostedZoneId,
		"The zone id the Route 53 binds to.",
	)

	fs.StringVar(&opt.KubeClusterName, "kube-cluster-name", opt.KubeClusterName,
		"The name of current kubernetes cluster.",
	)
	fs.StringVar(&opt.RemoteWriteUrl, "remote-write-url", opt.RemoteWriteUrl,
		"The address the prometheus remote write to.",
	)
	fs.StringVar(&opt.TiDBAccessCIDR, "tidb-access-cidr", "10.0.0.0/16",
		"(DEPRECATED) The address the tidb can be access from, it is deprecated and please use --load-balancer-cidrs",
	)
	fs.DurationVar(&opt.EvictPodDuration, "evict-pod-duration", 8*time.Hour,
		"The duration the TiDB cluster pods can be evicted. 0 means not to evict pods",
	)

	fs.StringVar(&opt.LoadBalancerClusterName, "load-balancer-cluster-name", opt.LoadBalancerClusterName,
		"Cluster Name of Load Balancer, default is same as --kube-cluster-name",
	)
	fs.StringSliceVar(&opt.LoadBalancerCIDRs, "load-balancer-cidrs", opt.LoadBalancerCIDRs,
		"CIDRs of Load Balancer",
	)

	fs.StringVar(&opt.AWSAccountID, "aws-account-id", opt.AWSAccountID,
		"The AWS account ID.")
	fs.StringVar(&opt.AWSRegion, "aws-region", opt.AWSRegion, "aws region")
	fs.StringVar(&opt.ImportRoleARN, "import-role-arn", opt.ImportRoleARN,
		"The ARN of the role used to assume roles provided by users for importing data")

	fs.StringVar(&opt.GCPProjectID, "gcp-project-id", opt.GCPProjectID, "gcp project id")
}

// Config returns config of manager
// Validation of options can be done in here.
func (opt *Options) Config() (*config.Config, error) {
	kubeconfig, err := clientcmd.BuildConfigFromFlags("", opt.Kubeconfig)
	if err != nil {
		return nil, fmt.Errorf("can't parse kubeconfig: %w", err)
	}
	infraKubeconfig, err := clientcmd.BuildConfigFromFlags("", opt.InfraKubeconfig)
	if err != nil {
		return nil, fmt.Errorf("can't parse infra kubeconfig: %w", err)
	}
	if opt.OIDCProviderARN == "" {
		return nil, fmt.Errorf("oidc-provider-arn must set")
	}
	if _, err := url.Parse(opt.ConfigServiceUrl); err != nil {
		return nil, fmt.Errorf("config-service-url parse err: %w", err)
	}
	if opt.BaseProxy == "" {
		return nil, fmt.Errorf("base-proxy must set")
	}
	if opt.PublicHostedZoneDomain == "" {
		return nil, fmt.Errorf("public-hosted-zone-domain must set")
	}
	if opt.PublicHostedZoneId == "" {
		return nil, fmt.Errorf("public-hosted-zone-id must set")
	}
	if opt.RemoteWriteUrl != "" {
		if _, err := url.Parse(opt.RemoteWriteUrl); err != nil {
			return nil, fmt.Errorf("remote-write-url parse err: %w", err)
		}
	}

	if opt.TiDBAccessCIDR != "" {
		opt.LoadBalancerCIDRs = append(opt.LoadBalancerCIDRs, opt.TiDBAccessCIDR)
	}

	if opt.EvictPodDuration < 0 {
		return nil, fmt.Errorf("evict-pod-duration can't be less than 0")
	}

	dnsPodSelector, err := metav1.ParseToLabelSelector(opt.DNSPodSelector)
	if err != nil {
		return nil, fmt.Errorf("can't parse dns pod selector %s: %w", opt.DNSPodSelector, err)
	}

	if opt.KubeClusterName == "" {
		return nil, fmt.Errorf("--kube-cluster-name must be specified")
	}

	for _, cidr := range opt.LoadBalancerCIDRs {
		if _, _, err = net.ParseCIDR(cidr); err != nil {
			return nil, fmt.Errorf("CIDR %s is not valid: %w", cidr, err)
		}
	}

	cfg := &config.Config{
		Kubeconfig:      kubeconfig,
		InfraKubeconfig: infraKubeconfig,

		MetricsBindAddress:     opt.MetricsBindAddress,
		WebhookPort:            opt.WebhookPort,
		HealthProbeBindAddress: opt.HealthProbeBindAddress,
		EnableLeaderElection:   opt.EnableLeaderElection,

		AWSAccountID:           opt.AWSAccountID,
		ImportRoleARN:          opt.ImportRoleARN,
		OIDCProviderARN:        opt.OIDCProviderARN,
		ConfigServiceUrl:       opt.ConfigServiceUrl,
		BaseProxy:              opt.BaseProxy,
		PublicHostedZoneDomain: opt.PublicHostedZoneDomain,
		PublicHostedZoneId:     opt.PublicHostedZoneId,

		AddonConfigMapNamespace: opt.AddonConfigMapNamespace,
		AddonConfigMapName:      opt.AddonConfigMapName,

		DNSPodSelector:          dnsPodSelector,
		DNSNamespace:            opt.DNSNamespace,
		KubeClusterName:         opt.KubeClusterName,
		RemoteWriteUrl:          opt.RemoteWriteUrl,
		EvictPodDuration:        opt.EvictPodDuration,
		LoadBalancerClusterName: opt.LoadBalancerClusterName,
		LoadBalancerCIDRs:       opt.LoadBalancerCIDRs,

		GCPProjectID: opt.GCPProjectID,
	}

	if cfg.LoadBalancerClusterName == "" {
		cfg.LoadBalancerClusterName = cfg.KubeClusterName
	}

	return cfg, nil
}
