package common

import (
	"context"
	"fmt"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	opv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	opconfig "github.com/pingcap/tidb-operator/pkg/apis/util/config"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

const (
	SEMKey = "security.enable-sem"
)

func asBool(v interface{}) (bool, error) {
	switch v := v.(type) {
	case bool:
		return v, nil
	default:
		return false, fmt.Errorf("unsupported type %T", v)
	}
}

// ErrWaitTidbInitialized tidb not initialized.
// SEM is enabled only when tidb is initialized.
var ErrWaitTidbInitialized = fmt.Errorf("tidb of cluster not initialized")

func ensureSEM(ctx context.Context, cluster *cloudv1.Cluster, tcCli client.Client, tc *v1alpha1.TidbCluster) error {
	if !tidbInitialized(cluster) {
		return ErrWaitTidbInitialized
	}

	var enable = false
	v := tc.Spec.TiDB.Config.Get(SEMKey).Interface()
	if v != nil {
		var err error
		enable, err = asBool(v)
		if err != nil {
			return err
		}
	}

	if enable {
		return nil
	}

	tc.Spec.TiDB.Config.Set(SEMKey, true)
	err := tcCli.Update(ctx, tc)
	if err != nil {
		return err
	}

	logger := log.FromContext(ctx)
	logger.Info("enable sem success")

	return nil
}

func anyPodEnableSEM(ctx context.Context, cli client.Client, tc *opv1alpha1.TidbCluster) (enable bool, err error) {
	pods, err := getTCTiDBPods(ctx, cli, tc)
	if err != nil {
		return false, fmt.Errorf("failed to get tidb pods: %w", err)
	}

	for _, pod := range pods {
		for _, v := range pod.Spec.Volumes {
			if v.Name != "config" {
				continue
			}

			if v.ConfigMap == nil {
				return false, fmt.Errorf("volume 'config' is not a configmap")
			}

			cmName := v.ConfigMap.Name
			cm := &corev1.ConfigMap{}
			err := cli.Get(ctx, types.NamespacedName{Namespace: tc.Namespace, Name: cmName}, cm)
			if err != nil {
				return false, fmt.Errorf("failed to get configmap: %w", err)
			}

			yamlData, ok := cm.Data["config-file"]
			if !ok {
				return false, fmt.Errorf("configmap %s has no data 'config-file'", cmName)
			}

			setted, err := isSEMSetted(yamlData)
			if err != nil {
				return false, fmt.Errorf("failed to check is sem setted: %w", err)
			}

			if setted {
				return true, nil
			}
		}
	}

	return false, nil
}

func isSEMSetted(yamlData string) (setted bool, err error) {
	// security.enable-sem
	mp := opconfig.New(make(map[string]interface{}))
	err = mp.UnmarshalTOML([]byte(yamlData))
	if err != nil {
		return false, fmt.Errorf("failed to UnmarshalTOML: %w", err)
	}

	v := mp.Get(SEMKey).Interface()
	if v == nil {
		return false, nil
	}

	enable, err := asBool(v)
	if err != nil {
		return false, err
	}

	return enable, nil
}

func getTCTiDBPods(ctx context.Context, cli client.Client, tc *opv1alpha1.TidbCluster) (pods []corev1.Pod, err error) {
	list := new(corev1.PodList)
	err = cli.List(ctx, list, client.InNamespace(tc.Namespace), client.MatchingLabels{
		consts.InstanceLabelKey:  tc.Name,
		consts.ComponentLabelKey: "tidb",
	})
	if err != nil {
		return nil, fmt.Errorf("failed to list tidb pods: %w", err)
	}

	return list.Items, nil
}

func setSEMforTiDBGroup(ctx context.Context, cluster *cloudv1.Cluster, tcCli client.Client, tc *v1alpha1.TidbCluster) error {
	var enable = false
	v := tc.Spec.TiDB.Config.Get(SEMKey).Interface()
	if v != nil {
		var err error
		enable, err = asBool(v)
		if err != nil {
			return err
		}
	}

	if enable {
		return nil
	}

	tc.Spec.TiDB.Config.Set(SEMKey, true)
	return nil
}
