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

package sprawl

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/hashicorp/consul/api"

	"github.com/hashicorp/consul/testing/deployer/topology"
)

func (s *Sprawl) ensureLicense() error {
	if s.license != "" {
		return nil
	}
	v, err := readLicense()
	if err != nil {
		return err
	}
	s.license = v
	return nil
}

func readLicense() (string, error) {
	if license := os.Getenv("CONSUL_LICENSE"); license != "" {
		return license, nil
	}

	licensePath := os.Getenv("CONSUL_LICENSE_PATH")
	if licensePath == "" {
		return "", nil
	}

	licenseBytes, err := os.ReadFile(licensePath)
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(licenseBytes)), nil
}

func (s *Sprawl) initTenancies(cluster *topology.Cluster) error {
	var (
		client = s.clients[cluster.Name]
		logger = s.logger.With("cluster", cluster.Name)
	)

	// TODO: change this to UPSERT

	var (
		partClient = client.Partitions()
		nsClient   = client.Namespaces()

		partitionNameList []string
	)
	for _, ap := range cluster.Partitions {
		if ap.Name != "default" {
			old, _, err := partClient.Read(context.Background(), ap.Name, nil)
			if err != nil {
				return fmt.Errorf("error reading partition %q: %w", ap.Name, err)
			}
			if old == nil {
				obj := &api.Partition{
					Name: ap.Name,
				}

				_, _, err := partClient.Create(context.Background(), obj, nil)
				if err != nil {
					return fmt.Errorf("error creating partition %q: %w", ap.Name, err)
				}
				logger.Debug("created partition", "partition", ap.Name)
			}

			partitionNameList = append(partitionNameList, ap.Name)
		}

		if err := s.createCrossNamespaceCatalogReadPolicies(cluster, ap.Name); err != nil {
			return fmt.Errorf("createCrossNamespaceCatalogReadPolicies[%s]: %w", ap.Name, err)
		}

		for _, ns := range ap.Namespaces {
			old, _, err := nsClient.Read(ns, &api.QueryOptions{Partition: ap.Name})
			if err != nil {
				return err
			}

			if old == nil {
				obj := &api.Namespace{
					Partition: ap.Name,
					Name:      ns,
					ACLs: &api.NamespaceACLConfig{
						PolicyDefaults: []api.ACLLink{
							{Name: "cross-ns-catalog-read"},
						},
					},
				}
				if ns == "default" {
					_, _, err := nsClient.Update(obj, nil)
					if err != nil {
						return err
					}
					logger.Debug("updated namespace", "namespace", ns, "partition", ap.Name)
				} else {
					_, _, err := nsClient.Create(obj, nil)
					if err != nil {
						return err
					}
					logger.Debug("created namespace", "namespace", ns, "partition", ap.Name)
				}
			}
		}
	}

	if err := s.waitUntilPartitionedSerfIsReady(context.TODO(), cluster, partitionNameList); err != nil {
		return fmt.Errorf("waitUntilPartitionedSerfIsReady: %w", err)
	}

	return nil
}

func (s *Sprawl) waitUntilPartitionedSerfIsReady(ctx context.Context, cluster *topology.Cluster, partitions []string) error {
	var (
		logger = s.logger.With("cluster", cluster.Name)
	)

	readyLogs := make(map[string]string)
	for _, partition := range partitions {
		readyLogs[partition] = `agent.server: Added serf partition to gossip network: partition=` + partition
	}

	start := time.Now()
	logger.Info("waiting for partitioned serf to be ready on all servers", "partitions", partitions)
	for _, node := range cluster.Nodes {
		if !node.IsServer() || node.Disabled {
			continue
		}

		var buf bytes.Buffer
		for {
			buf.Reset()

			err := s.runner.DockerExec(ctx, []string{
				"logs", node.DockerName(),
			}, &buf, nil)
			if err != nil {
				return fmt.Errorf("could not fetch docker logs from node %q: %w", node.ID(), err)
			}

			var (
				body  = buf.String()
				found []string
			)

			for partition, readyLog := range readyLogs {
				if strings.Contains(body, readyLog) {
					found = append(found, partition)
				}
			}

			if len(found) == len(readyLogs) {
				break
			}
		}

		time.Sleep(500 * time.Millisecond)
	}

	logger.Info("partitioned serf is ready on all servers", "partitions", partitions, "elapsed", time.Since(start))

	return nil
}
