// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package config

import (
	"strconv"
	"time"

	"github.com/hashicorp/raft"

	"github.com/hashicorp/consul/agent/checks"
	"github.com/hashicorp/consul/agent/consul"
	"github.com/hashicorp/consul/version"
)

// DefaultSource is the default agent configuration.
// This needs to be merged first in the head.
// TODO: return a LiteralSource (no decoding) instead of a FileSource
func DefaultSource() Source {
	cfg := consul.DefaultConfig()
	serfLAN := cfg.SerfLANConfig.MemberlistConfig
	serfWAN := cfg.SerfWANConfig.MemberlistConfig

	return FileSource{
		Name:   "default",
		Format: "hcl",
		Data: `
		acl = {
			token_ttl = "30s"
			policy_ttl = "30s"
			default_policy = "allow"
			down_policy = "extend-cache"
		}
		bind_addr = "0.0.0.0"
		bootstrap = false
		bootstrap_expect = 0
		check_output_max_size = ` + strconv.Itoa(checks.DefaultBufSize) + `
		check_update_interval = "5m"
		client_addr = "127.0.0.1"
		datacenter = "` + consul.DefaultDC + `"
		default_query_time = "300s"
		disable_coordinates = false
		disable_host_node_id = true
		disable_remote_exec = true
		domain = "consul."
		enable_central_service_config = true
		encrypt_verify_incoming = true
		encrypt_verify_outgoing = true
		log_level = "INFO"
		max_query_time = "600s"
		primary_gateways_interval = "30s"
		protocol = ` + strconv.Itoa(consul.DefaultRPCProtocol) + `
		retry_interval = "30s"
		retry_interval_wan = "30s"

		# segment_limit is the maximum number of network segments that may be declared. Default 64 is highly encouraged
		segment_limit = 64

		server = false
		server_rejoin_age_max = "168h"
		syslog_facility = "LOCAL0"

		tls = {
			defaults = {
				tls_min_version = "TLSv1_2"
			}
		}

		// TODO (slackpad) - Until #3744 is done, we need to keep these
		// in sync with agent/consul/config.go.
		autopilot = {
			cleanup_dead_servers = true
			last_contact_threshold = "200ms"
			max_trailing_logs = 250
			server_stabilization_time = "10s"
		}
		gossip_lan = {
			gossip_interval = "` + serfLAN.GossipInterval.String() + `"
			gossip_nodes = ` + strconv.Itoa(serfLAN.GossipNodes) + `
			retransmit_mult = ` + strconv.Itoa(serfLAN.RetransmitMult) + `
			probe_interval = "` + serfLAN.ProbeInterval.String() + `"
			probe_timeout = "` + serfLAN.ProbeTimeout.String() + `"
			suspicion_mult = ` + strconv.Itoa(serfLAN.SuspicionMult) + `
		}
		gossip_wan = {
			gossip_interval = "` + serfWAN.GossipInterval.String() + `"
			gossip_nodes = ` + strconv.Itoa(serfLAN.GossipNodes) + `
			retransmit_mult = ` + strconv.Itoa(serfLAN.RetransmitMult) + `
			probe_interval = "` + serfWAN.ProbeInterval.String() + `"
			probe_timeout = "` + serfWAN.ProbeTimeout.String() + `"
			suspicion_mult = ` + strconv.Itoa(serfWAN.SuspicionMult) + `
		}
		dns_config = {
			allow_stale = true
			a_record_limit = 0
			udp_answer_limit = 3
			max_stale = "87600h"
			recursor_timeout = "2s"
		}
		limits = {
			http_max_conns_per_client = 200
			https_handshake_timeout = "5s"
			request_limits = {
				mode = "disabled"
				read_rate = -1
				write_rate = -1
			}
			rpc_handshake_timeout = "5s"
			rpc_client_timeout = "60s"
			rpc_rate = -1
			rpc_max_burst = 1000
			rpc_max_conns_per_client = 100
			kv_max_value_size = ` + strconv.FormatInt(raft.SuggestedMaxDataSize, 10) + `
			txn_max_req_len = ` + strconv.FormatInt(raft.SuggestedMaxDataSize, 10) + `
		}
		performance = {
			leave_drain_time = "5s"
			raft_multiplier = ` + strconv.Itoa(int(consul.DefaultRaftMultiplier)) + `
			rpc_hold_timeout = "7s"
			grpc_keepalive_interval = "30s"
			grpc_keepalive_timeout = "20s"
		}
		ports = {
			dns = 8600
			http = 8500
			https = -1
			grpc = -1
			serf_lan = ` + strconv.Itoa(consul.DefaultLANSerfPort) + `
			serf_wan = ` + strconv.Itoa(consul.DefaultWANSerfPort) + `
			server = ` + strconv.Itoa(consul.DefaultRPCPort) + `
			proxy_min_port = 20000
			proxy_max_port = 20255
			sidecar_min_port = 21000
			sidecar_max_port = 21255
			expose_min_port = 21500
			expose_max_port = 21755
		}
		raft_protocol = 3
		telemetry = {
			metrics_prefix = "consul"
			filter_default = true
			prefix_filter = []
			retry_failed_connection = true
		}
		raft_snapshot_threshold = ` + strconv.Itoa(int(cfg.RaftConfig.SnapshotThreshold)) + `
		raft_snapshot_interval =  "` + cfg.RaftConfig.SnapshotInterval.String() + `"
		raft_trailing_logs = ` + strconv.Itoa(int(cfg.RaftConfig.TrailingLogs)) + `
		raft_logstore {
			wal {
				segment_size_mb = 64
			}
		}
		xds {
			update_max_per_second = 250
		}

		connect = {
			enabled = true
		}

		peering = {
			enabled = true
		}
	`,
	}
}

// DevSource is the additional default configuration for dev mode.
// This should be merged in the head after the default configuration.
// TODO: return a LiteralSource (no decoding) instead of a FileSource
func DevSource() Source {
	return FileSource{
		Name:   "dev",
		Format: "hcl",
		Data: `
		bind_addr = "127.0.0.1"
		disable_anonymous_signature = true
		disable_keyring_file = true
		enable_debug = true
		ui_config {
			enabled = true
		}
		log_level = "DEBUG"
		server = true

		gossip_lan = {
			gossip_interval = "100ms"
			probe_interval = "100ms"
			probe_timeout = "100ms"
			suspicion_mult = 3
		}
		gossip_wan = {
			gossip_interval = "100ms"
			probe_interval = "100ms"
			probe_timeout = "100ms"
			suspicion_mult = 3
		}
		connect = {
			enabled = true
		}

		peering = {
			enabled = true
		}

		performance = {
			raft_multiplier = 1
		}
		ports = {
			grpc = 8502
		}
		experiments = []
	`,
	}
}

// NonUserSource contains the values the user cannot configure.
// This needs to be merged in the tail.
// TODO: return a LiteralSource (no decoding) instead of a FileSource
func NonUserSource() Source {
	return FileSource{
		Name:   "non-user",
		Format: "hcl",
		Data: `
		check_deregister_interval_min = "1m"
		check_reap_interval = "30s"
		ae_interval = "1m"
		sync_coordinate_rate_target = 64
		sync_coordinate_interval_min = "15s"

		# SegmentNameLimit is the maximum segment name length.
		segment_name_limit = 64

		connect = {
			# 0s causes the value to be ignored and operate without capping
			# the max time before leaf certs can be generated after a roots change.
			test_ca_leaf_root_change_spread = "0s"
		}

		peering = {
			# We use peer registration for various testing
			test_allow_peer_registrations = false
		}
	`,
	}
}

// versionSource creates a config source for the version parameters.
// This should be merged in the tail since these values are not
// user configurable.
func versionSource(rev, ver, verPre, meta string, buildDate time.Time) Source {
	return LiteralSource{
		Name: "version",
		Config: Config{
			Revision:          &rev,
			Version:           &ver,
			VersionPrerelease: &verPre,
			VersionMetadata:   &meta,
			BuildDate:         &buildDate,
		},
	}
}

// defaultVersionSource returns the version config source for the embedded
// version numbers.
func defaultVersionSource() Source {
	buildDate, _ := time.Parse(time.RFC3339, version.BuildDate) // This has been checked elsewhere
	return versionSource(version.GitCommit, version.Version, version.VersionPrerelease, version.VersionMetadata, buildDate)
}

// DefaultConsulSource returns the default configuration for the consul agent.
// This should be merged in the tail since these values are not user configurable.
// TODO: return a LiteralSource (no decoding) instead of a FileSource
func DefaultConsulSource() Source {
	cfg := consul.DefaultConfig()
	raft := cfg.RaftConfig
	return FileSource{
		Name:   "consul",
		Format: "hcl",
		Data: `
		consul = {
			coordinate = {
				update_batch_size = ` + strconv.Itoa(cfg.CoordinateUpdateBatchSize) + `
				update_max_batches = ` + strconv.Itoa(cfg.CoordinateUpdateMaxBatches) + `
				update_period = "` + cfg.CoordinateUpdatePeriod.String() + `"
			}
			raft = {
				election_timeout = "` + raft.ElectionTimeout.String() + `"
				heartbeat_timeout = "` + raft.HeartbeatTimeout.String() + `"
				leader_lease_timeout = "` + raft.LeaderLeaseTimeout.String() + `"
			}
			server = {
				health_interval = "` + cfg.ServerHealthInterval.String() + `"
			}
		}
	`,
	}
}

// DevConsulSource returns the consul agent configuration for the dev mode.
// This should be merged in the tail after the DefaultConsulSource.
func DevConsulSource() Source {
	c := Config{}
	c.Consul.Coordinate.UpdatePeriod = strPtr("100ms")
	c.Consul.Raft.ElectionTimeout = strPtr("52ms")
	c.Consul.Raft.HeartbeatTimeout = strPtr("35ms")
	c.Consul.Raft.LeaderLeaseTimeout = strPtr("20ms")
	c.Consul.Server.HealthInterval = strPtr("10ms")
	return LiteralSource{Name: "consul-dev", Config: c}
}

func strPtr(v string) *string {
	return &v
}