package config

import (
	"fmt"
	"os"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/yaml"
)

type DatabaseProbeConfig struct {
	// ClusterID is be used as the value of label `cluster_id` in the metrics to identify the cluster.
	// Default to `target.clusterID` in the root config.
	ClusterID string `json:"clusterID,omitempty"`
	// Addrs are the addresses of the database.
	// All probers will use the latest available addr to access the database.
	Addrs []string `json:"addrs"`
	// UserFile is the file path to read the db user.
	UserFile string `json:"userFile"`
	// PasswordFile is the file path to read the db password.
	PasswordFile *string `json:"passwordFile,omitempty"`
	// ProbeTimeout is the max duration for each probe.
	// Default to 30s.
	ProbeTimeout *metav1.Duration `json:"timeout,omitempty"`
}

func (c *DatabaseProbeConfig) Complete(base *RootConfig) error {
	// complete
	if base != nil && base.Target != nil {
		if c.ClusterID == "" {
			c.ClusterID = base.Target.ClusterID
		}
	}
	if c.ProbeTimeout == nil {
		c.ProbeTimeout = &metav1.Duration{Duration: time.Second * 30}
	}

	// validate
	if len(c.Addrs) == 0 {
		return fmt.Errorf("empty addrs")
	}
	if c.UserFile == "" {
		return fmt.Errorf("empty userFile")
	}
	if c.ClusterID == "" {
		return fmt.Errorf("empty clusterID")
	}
	return nil
}

type InfraProberConfig struct {
	// Name is the name of the Cluster CR.
	// Default to `target.clusterName` in the root config.
	ClusterName string `json:"clusterName"`
	// Namespace is the namespace of the Cluster CR.
	// Default to `target.clusterNamespace` in the root config.
	ClusterNamespace string `json:"clusterNamespace"`
	// ClusterID is be used as the value of label `cluster_id` in the metrics to identify the cluster.
	// Default to `target.clusterID` in the root config.
	ClusterID string `json:"clusterID,omitempty"`
}

func (c *InfraProberConfig) Complete(base *RootConfig) error {
	// complete
	if base != nil && base.Target != nil {
		if c.ClusterName == "" {
			c.ClusterName = base.Target.ClusterName
		}
		if c.ClusterNamespace == "" {
			c.ClusterNamespace = base.Target.ClusterNamespace
		}
		if c.ClusterID == "" {
			c.ClusterID = base.Target.ClusterID
		}
	}

	// validate
	if c.ClusterName == "" {
		return fmt.Errorf("empty name")
	}
	if c.ClusterNamespace == "" {
		return fmt.Errorf("empty namespace")
	}
	if c.ClusterID == "" {
		return fmt.Errorf("empty clusterID")
	}
	return nil
}

type PlatformProberConfig struct {
	// ClusterID is the ID of the cluster in the TiDB Cloud.
	// Default to `target.clusterID` in the root config.
	ClusterID string `json:"clusterID"`
	// ProjectID is the ID of the project in the TiDB Cloud.
	// Default to `target.projectID` in the root config.
	ProjectID string `json:"projectID"`
	// PublicKeyFile is the file path to read the public key.
	PublicKeyFile string `json:"publicKeyFile"`
	// PrivateKeyFile is the file path to read the private key.
	PrivateKeyFile string `json:"privateKeyFile"`
	// PlatformAddr is the address of the TiDB Cloud apiserver .
	PlatformAddr string `json:"platformAddr,omitempty"`
}

func (c *PlatformProberConfig) Complete(base *RootConfig) error {
	// complete
	if base != nil && base.Target != nil {
		if c.ClusterID == "" {
			c.ClusterID = base.Target.ClusterID
		}
		if c.ProjectID == "" {
			c.ProjectID = base.Target.ProjectID
		}
	}
	if c.PlatformAddr == "" {
		c.PlatformAddr = "https://api.tidbcloud.com"
	}

	// validate
	if c.ClusterID == "" {
		return fmt.Errorf("empty clusterID")
	}
	if c.ProjectID == "" {
		return fmt.Errorf("empty projectID")
	}
	if c.PublicKeyFile == "" {
		return fmt.Errorf("empty publicKeyFile")
	}
	if c.PrivateKeyFile == "" {
		return fmt.Errorf("empty privateKeyFile")
	}
	return nil
}

type ProberConfig struct {
	Database *DatabaseProbeConfig  `json:"database,omitempty"`
	Infra    *InfraProberConfig    `json:"infra,omitempty"`
	Platform *PlatformProberConfig `json:"platform,omitempty"`
}

type TargetCluster struct {
	ClusterID        string `json:"clusterID"`
	ProjectID        string `json:"projectID"`
	ClusterName      string `json:"clusterName"`
	ClusterNamespace string `json:"clusterNamespace"`
}

type RootConfig struct {
	ListenAddr      string               `json:"listenAddr"`
	MetricPath      string               `json:"metricPath"`
	Predicate       metav1.LabelSelector `json:"predicate,omitempty"`
	InfraKubeConfig *string              `json:"infraKubeConfig,omitempty"`
	Target          *TargetCluster       `json:"target,omitempty"`

	Probers ProberConfig `json:"probers"`
}

func LoadRootConfig(path string) (*RootConfig, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}
	config := &RootConfig{}
	err = yaml.Unmarshal(data, config)
	if err != nil {
		return nil, err
	}

	return config, nil
}
