package gardener

import (
	"context"
	"fmt"

	"github.com/pingcap/errors"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

	"github.com/tidbcloud/infra-api/pkg/consts"
	"github.com/tidbcloud/infra-api/pkg/k8s"
)

const (
	localSLIReporter = "dbaas-sli-reporter"
)

// deployTiDBClusterSLIReporter deploys the SLI Reporter to the "tidb-admin" namespace.
// The SLI Reporter scrapes metrics from TiDB/TiKV/PD/TiFlash components' pods via `/metrics`.
// See https://github.com/tidbcloud/sli-reporter for more details.
func deployTiDBClusterSLIReporter(
	ctx context.Context,
	cli client.Client,
	imageRepoPrefix string,
	image string,
	cloudProvider string,
	reporterRoleARN string,
	deliveryStreamName string,
	deliveryStreamRoleArn string,
	region string,
	identityImage string,
) (op controllerutil.OperationResult, err error) {
	reporterLabels := map[string]string{
		"app": localSLIReporter,
	}
	cr := &rbacv1.ClusterRole{
		ObjectMeta: metav1.ObjectMeta{
			Name:   localSLIReporter,
			Labels: reporterLabels,
		},
	}
	_, err = controllerutil.CreateOrUpdate(context.TODO(), cli, cr, func() error {
		cr.Rules = []rbacv1.PolicyRule{
			{
				APIGroups: []string{""},
				Resources: []string{"pods", "services", "nodes", "secrets"},
				Verbs:     []string{"get", "watch", "list"},
			},
			{
				APIGroups: []string{"pingcap.com"},
				Resources: []string{"tidbclusters"},
				Verbs:     []string{"get", "watch", "list"},
			},
			{
				APIGroups: []string{"batch"},
				Resources: []string{"jobs"},
				Verbs:     []string{"get", "watch", "list"},
			},
		}
		return nil
	})
	if err != nil {
		return op, err
	}

	sa := &corev1.ServiceAccount{
		ObjectMeta: metav1.ObjectMeta{
			Name:      localSLIReporter,
			Namespace: consts.LocalOperatorNamespace,
			Labels:    reporterLabels,
		},
	}
	_, err = k8s.CreateIgnoreExists(ctx, cli, sa)
	if err != nil {
		return op, err
	}

	crb := &rbacv1.ClusterRoleBinding{
		ObjectMeta: metav1.ObjectMeta{
			Name:   localSLIReporter,
			Labels: reporterLabels,
		},
		Subjects: []rbacv1.Subject{{
			Kind:      rbacv1.ServiceAccountKind,
			Name:      localSLIReporter,
			Namespace: consts.LocalOperatorNamespace,
		}},
		RoleRef: rbacv1.RoleRef{
			Kind:     "ClusterRole",
			Name:     localSLIReporter,
			APIGroup: rbacv1.GroupName,
		},
	}
	_, err = k8s.CreateIgnoreExists(ctx, cli, crb)
	if err != nil {
		return op, err
	}

	deploy := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name:      localSLIReporter,
			Namespace: consts.LocalOperatorNamespace,
		},
		Spec: appsv1.DeploymentSpec{
			Selector: &metav1.LabelSelector{
				MatchLabels: reporterLabels,
			},
			Template: corev1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: reporterLabels,
				},
			},
		},
	}
	op, err = controllerutil.CreateOrUpdate(ctx, cli, deploy, func() error {
		deploy.Spec.Replicas = pointer.Int32(1)
		deploy.Spec.Template.Spec = corev1.PodSpec{
			RestartPolicy:      corev1.RestartPolicyAlways,
			ServiceAccountName: localSLIReporter,
		}
		reporterContainer := corev1.Container{
			Name:  localSLIReporter,
			Image: fmt.Sprintf("%s/%s", imageRepoPrefix, image),
			Command: []string{
				"/sli-reporter",
			},
			Resources: corev1.ResourceRequirements{
				Limits: corev1.ResourceList{
					corev1.ResourceCPU:    resource.MustParse("1"),
					corev1.ResourceMemory: resource.MustParse("1024Mi"),
				},
				Requests: corev1.ResourceList{
					corev1.ResourceCPU:    resource.MustParse("0.1"),
					corev1.ResourceMemory: resource.MustParse("80Mi"),
				},
			},
			Env: []corev1.EnvVar{
				{
					Name:  "AWS_REGION",
					Value: region,
				},
				{
					Name:  "AWS_DELIVERY_STREAM_NAME",
					Value: deliveryStreamName,
				},
				{
					Name:  "AWS_DELIVERY_STREAM_ROLE_ARN",
					Value: deliveryStreamRoleArn,
				},
			},
		}

		if cloudProvider == consts.ProviderTypeGCP {
			sidecar, vol, volMount, envs := k8s.PrepareGCP2AWSIdentitySidecar(reporterRoleARN, identityImage)
			deploy.Spec.Template.Spec.Volumes = append(deploy.Spec.Template.Spec.Volumes, vol)
			reporterContainer.Env = append(reporterContainer.Env, envs...)
			reporterContainer.VolumeMounts = append(reporterContainer.VolumeMounts, volMount)
			deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, sidecar)
		}

		deploy.Spec.Template.Spec.Containers = append(deploy.Spec.Template.Spec.Containers, reporterContainer)
		return nil
	})

	if err != nil {
		return op, errors.AddStack(err)
	}

	return op, nil
}
