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

package sprawl

import (
	"bytes"
	"context"
	"fmt"
	"io"

	"github.com/hashicorp/consul/testing/deployer/sprawl/internal/secrets"
	"github.com/hashicorp/consul/testing/deployer/topology"
)

const (
	consulUID     = "100"
	consulGID     = "1000"
	consulUserArg = consulUID + ":" + consulGID
)

func tlsPrefixFromNode(node *topology.Node) string {
	switch node.Kind {
	case topology.NodeKindServer:
		return node.Partition + "." + node.Name + ".server"
	case topology.NodeKindClient:
		return node.Partition + "." + node.Name + ".client"
	default:
		return ""
	}
}

func tlsCertCreateCommand(node *topology.Node) string {
	if node.IsServer() {
		return fmt.Sprintf(`consul tls cert create -server -dc=%s -node=%s`, node.Datacenter, node.PodName())
	} else {
		return fmt.Sprintf(`consul tls cert create -client -dc=%s`, node.Datacenter)
	}
}

func (s *Sprawl) initTLS(ctx context.Context) error {
	for _, cluster := range s.topology.Clusters {

		var buf bytes.Buffer

		// Create the CA if not already done, and proceed to do all of the
		// consul CLI calls inside of a throwaway temp directory.
		buf.WriteString(`
if [[ ! -f consul-agent-ca-key.pem || ! -f consul-agent-ca.pem ]]; then
	consul tls ca create
fi
rm -rf tmp
mkdir -p tmp
cp -a consul-agent-ca-key.pem consul-agent-ca.pem tmp
cd tmp
`)

		for _, node := range cluster.Nodes {
			if !node.IsAgent() || node.Disabled {
				continue
			}

			node.TLSCertPrefix = tlsPrefixFromNode(node)
			if node.TLSCertPrefix == "" {
				continue
			}

			expectPrefix := cluster.Datacenter + "-" + string(node.Kind) + "-consul-0"

			// Conditionally generate these in isolation and rename them to
			// not rely upon the numerical indexing.
			buf.WriteString(fmt.Sprintf(`
if [[ ! -f %[1]s || ! -f %[2]s ]]; then
	rm -f %[3]s %[4]s
	%[5]s
	mv -f %[3]s %[1]s
	mv -f %[4]s %[2]s
fi
`,
				"../"+node.TLSCertPrefix+"-key.pem", "../"+node.TLSCertPrefix+".pem",
				expectPrefix+"-key.pem", expectPrefix+".pem",
				tlsCertCreateCommand(node),
			))
		}

		err := s.runner.DockerExec(ctx, []string{
			"run",
			"--rm",
			"-i",
			"--net=none",
			"-v", cluster.TLSVolumeName + ":/data",
			// TODO: latest busted?
			// https://hashicorp.slack.com/archives/C03EUN3QF1C/p1691784078972959
			"busybox:1.34",
			"sh", "-c",
			// Need this so the permissions stick; docker seems to treat unused volumes differently.
			`touch /data/VOLUME_PLACEHOLDER && chown -R ` + consulUserArg + ` /data`,
		}, io.Discard, nil)
		if err != nil {
			return fmt.Errorf("could not initialize docker volume for cert data %q: %w", cluster.TLSVolumeName, err)
		}

		err = s.runner.DockerExec(ctx, []string{"run",
			"--rm",
			"-i",
			"--net=none",
			"-u", consulUserArg,
			"-v", cluster.TLSVolumeName + ":/data",
			"-w", "/data",
			"--entrypoint", "",
			cluster.Images.Consul,
			"/bin/sh", "-ec", buf.String(),
		}, io.Discard, nil)
		if err != nil {
			return fmt.Errorf("could not create all necessary TLS certificates in docker volume: %v", err)
		}

		var capture bytes.Buffer
		err = s.runner.DockerExec(ctx, []string{"run",
			"--rm",
			"-i",
			"--net=none",
			"-u", consulUserArg,
			"-v", cluster.TLSVolumeName + ":/data",
			"-w", "/data",
			"busybox:1.34",
			"cat",
			"/data/consul-agent-ca.pem",
		}, &capture, nil)
		if err != nil {
			return fmt.Errorf("could not read CA PEM from docker volume: %v", err)
		}

		caPEM := capture.String()
		if caPEM == "" {
			return fmt.Errorf("found empty CA PEM")
		}

		s.secrets.SaveGeneric(cluster.Name, secrets.CAPEM, caPEM)
	}

	return nil
}
