package ccb_application_router

import (
	"fmt"
	"math/rand"
	"strings"
	"time"
)

var DeployUser = "root"
var DeployDir = "/data/deploy"
var DataDir = "/data/data"

// TODO: readpool.unified.max-thread-count 7->10
var TiupTopoTemplate = `
global:
  user: %s
  ssh_port: 22
  deploy_dir: "%s"
  data_dir: "%s"
  arch: "amd64"
server_configs:
  pd:
    lease: 1
    election-interval: 1s
    tick-interval: 200ms
    replication.max-replicas: %d
    replication.location-labels: [zone, host]
    pd-server.dashboard-address: none
    pd-server.trace-region-flow: false
  tikv:
    pd.enable-forwarding: true
    raftdb.defaultcf.compression-per-level: [lz4, lz4, lz4, lz4, lz4, lz4, lz4]
    raftdb.defaultcf.hard-pending-compaction-bytes-limit: 4096GB
    raftdb.defaultcf.level0-slowdown-writes-trigger: 32
    raftdb.defaultcf.level0-stop-writes-trigger: 1024
    raftdb.defaultcf.max-write-buffer-number: 1024
    raftdb.defaultcf.soft-pending-compaction-bytes-limit: 4096GB
    raftdb.defaultcf.write-buffer-size: 256MB
    raftdb.max-background-jobs: 4
    raftdb.max-sub-compactions: 2
    raftdb.use-direct-io-for-flush-and-compaction: true
    raftstore.apply-pool-size: 6
    raftstore.hibernate-regions: false
    raftstore.pd-heartbeat-tick-interval: 5s
    raftstore.raft-base-tick-interval: 200ms
    raftstore.raft-election-timeout-ticks: 5
    raftstore.raft-heartbeat-ticks: 1
    raftstore.raft-max-inflight-msgs: 2048
    raftstore.raft-store-max-leader-lease: 800ms
    raftstore.store-pool-size: 4
    readpool.storage.use-unified-pool: true
    readpool.unified.max-thread-count: 7
    rocksdb.defaultcf.block-size: 8KB
    rocksdb.defaultcf.compression-per-level: [lz4, lz4, lz4, lz4, lz4, lz4, lz4]
    rocksdb.defaultcf.hard-pending-compaction-bytes-limit: 4096GB
    rocksdb.defaultcf.level0-slowdown-writes-trigger: 32
    rocksdb.defaultcf.level0-stop-writes-trigger: 1024
    rocksdb.defaultcf.max-bytes-for-level-base: 1GB
    rocksdb.defaultcf.max-write-buffer-number: 16
    rocksdb.defaultcf.soft-pending-compaction-bytes-limit: 4096GB
    rocksdb.defaultcf.write-buffer-size: 1024MB
    rocksdb.max-background-jobs: 16
    rocksdb.max-sub-compactions: 4
    rocksdb.rate-bytes-per-sec: 500MB
    rocksdb.use-direct-io-for-flush-and-compaction: true
    server.grpc-raft-conn-num: 3%s
    storage.enable-ttl: true
    storage.ttl-check-poll-interval: 24h
monitored:
  node_exporter_port: 9300
  blackbox_exporter_port: 9315
monitoring_servers:
  - host: tiup-0.tiup-peer
    port: 9666
grafana_servers:
  - host: tiup-0.tiup-peer
    port: 6330
pd_servers:
%s

%s
 
tikv_servers:
%s
`

var TiDBNodeTemplateInTopo = `  - host: tidb-%v.tidb-peer
    port: 4000
    status_port: 10080
`

var PDTemplateInTopo = `  - host: pd-%v.pd-peer
    client_port: 2579
    peer_port: 2580
`

var TiKVTemplateInTopo = `  - host: tikv-%v.tikv-peer
    port: 20360
    status_port: 20380
    config:
        server.labels: {host: tikv-%v.tikv-peer, zone: az%v}
`

func GetStorageAPIVersion(APIVersion int, ClusterVersion string) string {
	if (strings.HasPrefix(ClusterVersion, "v") && strings.Compare(ClusterVersion, "v6.1.0") >= 0) || (strings.HasPrefix(ClusterVersion, "nightly")) {
		return fmt.Sprintf("\n    storage.api-version: %d", APIVersion)
	} else {
		return ""
	}
}
func GetRawKVClusterTiKVTopo(TiKVCount int, ZoneCount int) string {
	var TiKVTopo string
	for TiKVIndex := 0; TiKVIndex < TiKVCount; TiKVIndex++ {
		TiKVTopo += fmt.Sprintf(TiKVTemplateInTopo, TiKVIndex, TiKVIndex, TiKVIndex%ZoneCount+1)
	}
	return TiKVTopo
}

func GetRawKVClusterTiDBTopo(TiDBCount int, ClusterVersion string, APIVersion int) string {
	TiDBTopo := ""
	if TiDBCount > 0 && APIVersion == 2 {
		if (strings.HasPrefix(ClusterVersion, "v") && strings.Compare(ClusterVersion, "v6.1.0") >= 0) || (strings.HasPrefix(ClusterVersion, "nightly")) {
			TiDBTopo += "tidb_servers:\n"
			for i := 0; i < TiDBCount; i++ {
				TiDBTopo += fmt.Sprintf(TiDBNodeTemplateInTopo, i)
			}
		}
	}
	return TiDBTopo
}

func GetRawKVClusterPDTopo(PDCount int) string {
	PDTopo := fmt.Sprintf(PDTemplateInTopo, 0)
	for i := 1; i < PDCount; i++ {
		PDTopo += fmt.Sprintf(PDTemplateInTopo, i)
	}
	return PDTopo
}

type InstancesCount struct {
	TiDBCount         int
	TiKVCount         int
	PDCount           int
	ZoneCount         int
	WorkloadNodeCount int
	ReplicaCount      int
}

type NodeInfo struct {
	Host    string `json: "host"`
	Role    string `json: "role"`
	Zone    string `json: "zone"`
	Ports   []int  `json: "ports`
	DataDir string `json: "data-dir"`
}

type ClusterInfo struct {
	NodesInfo         []NodeInfo `json:"NodesInfo"`
	AllPorts          []int      `json:"AllPorts"`
	GrafanaAddr       string     `json:"GrafanaAddr"`
	PrometheusAddr    string     `json:"PrometheusAddr"`
	WorkloadNodeAddrs []string   `json:"WorkloadNodeAddrs"`
	ScaleInTiKVNodes  string     `json:"ScaleInTiKVNodes"`
	ScaleOutTiKVYML   string     `json:"ScaleOutTiKVYML"`
}

func GetNodeInfo(PDCount int, TiKVCount int, ZoneCount int) []NodeInfo {
	nodesInfo := make([]NodeInfo, 0)

	for i := 0; i < PDCount; i++ {
		node := NodeInfo{
			Host:    fmt.Sprintf("pd-%v.pd-peer", i),
			Role:    "pd",
			Zone:    fmt.Sprintf("az%v", i%ZoneCount+1),
			Ports:   []int{2579, 2580},
			DataDir: DataDir,
		}
		nodesInfo = append(nodesInfo, node)
	}
	for i := 0; i < TiKVCount; i++ {
		node := NodeInfo{
			Host:    fmt.Sprintf("tikv-%v.tikv-peer", i),
			Role:    "tikv",
			Zone:    fmt.Sprintf("az%v", i%ZoneCount+1),
			Ports:   []int{20360, 20380},
			DataDir: DataDir,
		}
		nodesInfo = append(nodesInfo, node)
	}

	return nodesInfo
}

func GetWorkloadNodeInfo(WorkloadNodeCount int) []string {
	workloadNodeAddrs := make([]string, WorkloadNodeCount)
	for i := 0; i < WorkloadNodeCount; i++ {
		workloadNodeAddrs[i] = fmt.Sprintf("workload0-%v.workload0-peer", i)
	}
	return workloadNodeAddrs
}

// Scale-in one tikv per zone
func RandomScaleInTiKVNodePerZone(instancesCount InstancesCount) []int {
	TiKVNodesIndex := make([]int, instancesCount.ZoneCount)
	step := instancesCount.ZoneCount
	TiKVCountPerZone := instancesCount.TiKVCount / instancesCount.ZoneCount
	seed := rand.NewSource(time.Now().UnixNano())
	r := rand.New(seed)
	for i := 0; i < instancesCount.ZoneCount; i++ {
		index := i + step*r.Intn(TiKVCountPerZone)
		TiKVNodesIndex[i] = index
	}
	return TiKVNodesIndex
}

func GetScaleInTiKVNodes(TiKVNodesIndex []int) string {
	scaleInTiKVTemplate := "tikv-%v.tikv-peer:20360"
	if len(TiKVNodesIndex) > 0 {
		scaleInTikvNodes := fmt.Sprintf(scaleInTiKVTemplate, TiKVNodesIndex[0])
		for i := 1; i < len(TiKVNodesIndex); i++ {
			scaleInTikvNodes += ","
			scaleInTikvNodes += fmt.Sprintf(scaleInTiKVTemplate, TiKVNodesIndex[i])
		}
		return scaleInTikvNodes
	}
	return ""
}

func GetScaleOutTiKVNodesYML(TiKVNodesIndex []int, ZoneCount int) string {
	if len(TiKVNodesIndex) > 0 {
		ScaleOutTiKVNodesYML := "tikv_servers:\n"

		for i := 0; i < len(TiKVNodesIndex); i++ {
			ScaleOutTiKVNodesYML += fmt.Sprintf(TiKVTemplateInTopo, TiKVNodesIndex[i], TiKVNodesIndex[i], TiKVNodesIndex[i]%ZoneCount+1)
		}
		return ScaleOutTiKVNodesYML
	}

	return ""
}

var PlacementRuleTemplate = `    {
        "group_id": "pd",
        "id": "az%v",
        "start_key": "",
        "end_key": "",
        "role": "%s",
        "count": %v,
        "label_constraints": [
            {
                "key": "zone",
                "op": "in",
                "values": [
                    "az%v"
                ]
            }
        ],
        "location_labels": [
            "zone",
            "host"
        ]
    },
`

// In topo yaml, replication.max-replicas = 5
// If ZoneCount = 4, 2 voters in both AZ1 and AZ2; 1 follower in AZ3, 1 learner in AZ4.
// If ZoneCount = 3, 2 voters in both AZ1 and AZ2; 1 follower in AZ3.
func GeneratePlacementRules(TiKVCount int, ZoneCount int) string {
	// TODO: suggest 3 or 4 zones, so far,
	//  for 1 or 2 zones, do not set placement rule.
	if ZoneCount < 3 {
		return ""
	}
	PlacementRule := ""
	TiKVCountPerZone := TiKVCount / ZoneCount
	VotersPerZone := 2
	if TiKVCountPerZone < VotersPerZone {
		VotersPerZone = TiKVCountPerZone
	}
	// 2 voters placement rules
	for i := 1; i <= 2; i++ {
		PlacementRule += fmt.Sprintf(PlacementRuleTemplate, i, "voter", VotersPerZone, i)
	}
	// 1 follower placement rule
	PlacementRule += fmt.Sprintf(PlacementRuleTemplate, 3, "follower", 1, 3)
	if ZoneCount == 4 {
		// 1 learner placement rule
		PlacementRule += fmt.Sprintf(PlacementRuleTemplate, 4, "learner", 1, 4)
	}
	return "[\n" +
		"    {\n" +
		"      \"group_id\": \"pd\",\n" +
		"      \"group_index\": 0,\n" +
		"      \"group_override\": false,\n" +
		"      \"rules\": [" +
		PlacementRule[:len(PlacementRule)-2] +
		"]\n}\n]"
}

func GetRawKVClusterInfo(instancesCount InstancesCount, GrafanaAddr string, PrometheusAddr string) ClusterInfo {
	clusterInfo := ClusterInfo{
		NodesInfo:         GetNodeInfo(instancesCount.PDCount, instancesCount.TiKVCount, instancesCount.ZoneCount),
		AllPorts:          []int{2579, 2580, 20360, 20380},
		GrafanaAddr:       GrafanaAddr,
		PrometheusAddr:    PrometheusAddr,
		WorkloadNodeAddrs: GetWorkloadNodeInfo(instancesCount.WorkloadNodeCount),
	}
	TiKVNodesIndex := RandomScaleInTiKVNodePerZone(instancesCount)
	clusterInfo.ScaleInTiKVNodes = GetScaleInTiKVNodes(TiKVNodesIndex)
	clusterInfo.ScaleOutTiKVYML = GetScaleOutTiKVNodesYML(TiKVNodesIndex, instancesCount.ZoneCount)
	return clusterInfo
}
