package main

import (
	"fmt"
	"os"
	"strings"

	"github.com/tidbcloud/config-service/app/overlay/format"

	"github.com/pkg/errors"
	"github.com/tidbcloud/config-service/api/v1alpha1"
	"sigs.k8s.io/yaml"
)

const (
	UpdatePolicyDryrun    = "Dryrun"
	UpdatePolicyImmediate = "Immediate"
)

func runDiff(base, target string) error {
	bConfigs, err := readClusterConfigs(base)
	if err != nil {
		return err
	}
	tConfigs, err := readClusterConfigs(target)
	if err != nil {
		return err
	}
	var heading string

	var hasChange bool
	tenantCluster := map[string][]string{}
	for name, cc := range tConfigs {
		oldCC, ok := bConfigs[name]
		if !ok {
			// a new ClusterConfig is added
			// FIXME: name is not consistent, use labels instead
			// FIXME: recursion when new level added
			if strings.Contains(name, "--") {
				parentName := name[:strings.LastIndex(name, "--")]
				oldCC, ok = bConfigs[parentName]
				if !ok {
					if strings.Contains(parentName, "--") {
						parentName = parentName[:strings.LastIndex(parentName, "--")]
						oldCC, ok = bConfigs[parentName]
						if !ok {
							return errors.Errorf("%s not found in base config", name)
						}
					} else {
						return errors.Errorf("%s not found in base config", name)
					}
				}
			} else {
				return errors.Errorf("%s not found in base config", name)
			}
			heading = fmt.Sprintf("* New config for tenant: **%s** and cluster: **%s**, policy: **%s**. Diff to parent:", cc.Labels["tidbcloud.com/tenant"], cc.Labels["tidbcloud.com/cluster"], string(cc.Spec.UpdatePolicy))
		} else {
			// mutate current config
			heading = fmt.Sprintf("* Mutate config for tenant: **%s** and cluster: **%s**, policy: **%s**. Diff to old:", cc.Labels["tidbcloud.com/tenant"], cc.Labels["tidbcloud.com/cluster"], string(cc.Spec.UpdatePolicy))
		}

		if oldCC.Spec.UpdatePolicy == UpdatePolicyDryrun && cc.Spec.UpdatePolicy == UpdatePolicyImmediate {
			t := cc.Labels["tidbcloud.com/tenant"]
			c := cc.Labels["tidbcloud.com/cluster"]
			tenantCluster[t] = append(tenantCluster[t], c)
		}
		result := diffClusterConfig(oldCC, cc)
		if result != "" {
			hasChange = true
			fmt.Println(heading)
			fmt.Printf("```diff\n%s```\n", result)
		}
	}

	for name, cc := range bConfigs {
		_, ok := tConfigs[name]
		if !ok {
			// a clusterConfig is deleted
			hasChange = true
			fmt.Printf("* Delete clusterConfig for tenant: **%s** and cluster: **%s**, will fallback to parent.\n", cc.Labels["tidbcloud.com/tenant"], cc.Labels["tidbcloud.com/cluster"])
		}
	}

	if len(tenantCluster) > 0 {
		fmt.Printf("\nBesides the diff above, some clusters' updatePolicy are **transferred from Dryrun to Immediate**, they might be **restarted** due to accumulation of previous dryrun changes.\n")
	}
	for t, clusters := range tenantCluster {
		r := renderGrafanaUrl(t, clusters, baseUrl)
		fmt.Printf("\n* For clusters belong to tenant %s, please double-check the [GitOps Dryrun Clusters](%s) for safety.\n\n", t, r)
	}
	if !hasChange {
		fmt.Println("There is **NO** change to the target environment.")
	}
	return nil
}

func diffClusterConfig(base, target *v1alpha1.ClusterConfig) string {
	b := base.DeepCopy()
	t := target.DeepCopy()
	b.Spec.ConfigMeta = nil
	t.Spec.ConfigMeta = nil
	bs, _ := yaml.Marshal(b.Spec)
	ts, _ := yaml.Marshal(t.Spec)
	r := format.UnifiedDiff(format.DiffConfig{A: string(bs), B: string(ts)})
	return r
}

// https://grafana.oauth.prod.aws.tidbcloud.com/d/90I1Tiqnk/gitops-dryrun?orgId=1
// &var-tenant=1372813089188361282&var-cluster=All
// &var-tenant=1372813089188361282&var-cluster=111&var-cluster=1222
// nolint
func renderGrafanaUrl(tenant string, clusters []string, baseUrl string) string {
	var sb strings.Builder
	sb.WriteString(baseUrl)
	sb.WriteString("&var-tenant=")
	sb.WriteString(strings.Title(tenant))
	for _, c := range clusters {
		if c == "all" {
			sb.WriteString("&var-cluster=All")
			return sb.String()
		}
	}
	for _, c := range clusters {
		sb.WriteString("&var-cluster=")
		sb.WriteString(c)
	}

	return sb.String()
}

func readClusterConfigs(dir string) (map[string]*v1alpha1.ClusterConfig, error) {
	fis, err := os.ReadDir(dir)
	if err != nil {
		return nil, errors.Wrapf(err, "error reading %s", dir)
	}
	clusterConfigs := make(map[string]*v1alpha1.ClusterConfig)
	for _, fi := range fis {
		content, err := os.ReadFile(fmt.Sprintf("%s/%s", dir, fi.Name()))
		if err != nil {
			return nil, err
		}
		var cc v1alpha1.ClusterConfig
		if err := yaml.Unmarshal(content, &cc); err != nil {
			return nil, err
		}
		clusterConfigs[cc.Name] = &cc
	}

	return clusterConfigs, nil
}
