package envgroup

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/porter-dev/porter/api/types"
	"github.com/porter-dev/porter/internal/helm"
	"github.com/porter-dev/porter/internal/kubernetes"
	"github.com/stefanmcshane/helm/pkg/release"
	v1 "k8s.io/api/core/v1"
)

func ConvertV1ToV2EnvGroup(agent *kubernetes.Agent, name, namespace string) (*v1.ConfigMap, error) {
	cm, err := agent.GetConfigMap(name, namespace)
	if err != nil {
		return nil, err
	}

	secret, err := agent.GetSecret(name, namespace)
	if err != nil {
		return nil, err
	}

	variables := make(map[string]string)
	secretVariables := make(map[string]string)

	for key, val := range cm.Data {
		if strings.Contains(val, "PORTERSECRET") {
			secretVariables[key] = val
		} else {
			variables[key] = val
		}
	}

	for key, val := range secret.Data {
		secretVariables[key] = string(val)
	}

	envGroup, err := CreateEnvGroup(agent, types.ConfigMapInput{
		Name:            name,
		Namespace:       namespace,
		Variables:       variables,
		SecretVariables: secretVariables,
	})
	if err != nil {
		return nil, err
	}

	// delete the old configmap
	// note: we keep the old secret to ensure existing secret references are kept intact
	if err := agent.DeleteConfigMap(name, namespace); err != nil {
		return nil, err
	}

	return envGroup, nil
}

func CreateEnvGroup(agent *kubernetes.Agent, input types.ConfigMapInput) (*v1.ConfigMap, error) {
	// look for a latest configmap
	oldCM, latestVersion, err := agent.GetLatestVersionedConfigMap(input.Name, input.Namespace)

	if err != nil && !errors.Is(err, kubernetes.IsNotFoundError) {
		return nil, err
	} else if err != nil {
		latestVersion = 1
	} else {
		latestVersion += 1
	}

	apps := make([]string, 0)

	if oldCM != nil {
		oldEG, err := ToEnvGroup(oldCM)

		if err == nil {
			apps = oldEG.Applications
		}
	}

	oldSecret, _, err := agent.GetLatestVersionedSecret(input.Name, input.Namespace)

	if input.SecretVariables == nil {
		input.SecretVariables = make(map[string]string)
	}

	if err != nil && !errors.Is(err, kubernetes.IsNotFoundError) {
		return nil, err
	} else if err == nil && oldSecret != nil {
		// In this case, we find all old variables referencing a secret value, and add those
		// values to the new secret variables. The frontend will only send **new** secret values.
		for key1, val1 := range input.Variables {
			if strings.Contains(val1, "PORTERSECRET") {
				// get that value from the secret
				for key2, val2 := range oldSecret.Data {
					if key2 == key1 {
						input.SecretVariables[key1] = string(val2)
					}
				}
			}
		}
	}

	// add all secret env variables to configmap with value PORTERSECRET_${configmap_name}
	for key := range input.SecretVariables {
		input.Variables[key] = fmt.Sprintf("PORTERSECRET_%s.v%d", input.Name, latestVersion)
	}

	cm, err := agent.CreateVersionedConfigMap(input.Name, input.Namespace, latestVersion, input.Variables, apps...)
	if err != nil {
		return nil, err
	}

	secretData := EncodeSecrets(input.SecretVariables)

	// create secret first
	if _, err := agent.CreateLinkedVersionedSecret(input.Name, input.Namespace, cm.ObjectMeta.Name, latestVersion, secretData); err != nil {
		return nil, err
	}

	return cm, err
}

func ToEnvGroup(configMap *v1.ConfigMap) (*types.EnvGroup, error) {
	res := &types.EnvGroup{
		CreatedAt: configMap.ObjectMeta.CreationTimestamp.Time,
		Namespace: configMap.Namespace,
		Variables: configMap.Data,
	}

	// if the label "porter"="true" exists, this is a V1 env group
	porterLabel, porterLabelExists := configMap.Labels["porter"]

	if porterLabelExists && porterLabel == "true" {
		res.MetaVersion = 1
		res.Name = configMap.ObjectMeta.Name
		return res, nil
	}

	// set the meta version to 2 if porter label is not captured
	res.MetaVersion = 2

	// get the name
	name, nameExists := configMap.Labels["envgroup"]

	if !nameExists {
		return nil, fmt.Errorf("not a valid configmap: envgroup label does not exist")
	}

	res.Name = name

	// get the version
	versionLabelStr, versionLabelExists := configMap.Labels["version"]

	if !versionLabelExists {
		return nil, fmt.Errorf("not a valid configmap: version label does not exist")
	}

	versionInt, err := strconv.Atoi(versionLabelStr)
	if err != nil {
		return nil, fmt.Errorf("not a valid configmap, error converting version: %v", err)
	}

	res.Version = uint(versionInt)

	// get applications, if they exist
	appStr, appAnnonExists := configMap.Annotations[kubernetes.PorterAppAnnotationName]

	if appAnnonExists && appStr != "" {
		res.Applications = strings.Split(appStr, ",")
	} else {
		res.Applications = []string{}
	}

	return res, nil
}

func GetSyncedReleases(helmAgent *helm.Agent, configMap *v1.ConfigMap) ([]*release.Release, error) {
	res := make([]*release.Release, 0)

	// get applications, if they exist
	appStr, appAnnonExists := configMap.Annotations[kubernetes.PorterAppAnnotationName]

	if !appAnnonExists || appStr == "" {
		return res, nil
	}

	appStrArr := strings.Split(appStr, ",")

	// list all latest helm releases and check them against app string
	releases, err := helmAgent.ListReleases(context.Background(), configMap.Namespace, &types.ReleaseListFilter{
		StatusFilter: []string{
			"deployed",
			"uninstalled",
			"pending",
			"pending-install",
			"pending-upgrade",
			"pending-rollback",
			"failed",
		},
	})
	if err != nil {
		return nil, err
	}

	for _, rel := range releases {
		for _, appName := range appStrArr {
			if rel.Name == appName {
				res = append(res, rel)
			}
		}
	}

	return res, nil
}

func GetStackSyncedReleases(helmAgent *helm.Agent, namespace string) ([]*release.Release, error) {
	res := make([]*release.Release, 0)

	releases, err := helmAgent.ListReleases(context.Background(), namespace, &types.ReleaseListFilter{
		StatusFilter: []string{
			"deployed",
			"uninstalled",
			"pending",
			"pending-install",
			"pending-upgrade",
			"pending-rollback",
			"failed",
		},
	})
	if err != nil {
		return nil, err
	}

	res = append(res, releases...)

	return res, nil
}

func EncodeSecrets(data map[string]string) map[string][]byte {
	res := make(map[string][]byte)

	for key, rawValue := range data {
		res[key] = []byte(rawValue)
	}

	return res
}
