package aks

import (
	"context"
	"fmt"

	"k8s.io/utils/ptr"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/tidbcloud/infra-provider/pkg/applier/mutatefuncs"
	"github.com/upbound/provider-aws/apis/iam/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/infra-provider/pkg/applier"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/appbox/task"
)

const (
	reporterPolicyDocument = `{
  "Version": "2012-10-17",
  "Statement": [
	{
		"Effect":"Allow",
		"Action":[
			"sts:AssumeRole"
		],
		"Resource":[
			"arn:aws:iam::*:role/sli-cross-account-role"
		]
	}
  ]
}`
)

type SLIReporter struct {
	*task.BaseTask
	config *SLIReporterConfig
}

type SLIReporterConfig struct {
	*config.InfraComponentConfig
	awsRoleArn        string
	overwriteImageTag string
}

func (c *SLIReporterConfig) ToHelmValues() map[string]interface{} {
	awsValues := c.HelmValues["aws"].(map[string]interface{})
	// set the awsRoleArn created in this task
	awsValues["roleArn"] = c.awsRoleArn
	if c.overwriteImageTag != "" {
		image := c.HelmValues["image"]
		image.(map[string]interface{})["tag"] = c.overwriteImageTag
	}

	return c.HelmValues
}

func NewSLIReporter(ctx *task.Context) *SLIReporter {
	return &SLIReporter{
		BaseTask: &task.BaseTask{Context: ctx},
		config: &SLIReporterConfig{
			InfraComponentConfig: ctx.DedicatedTmplConfig.SLIReporter.DeepCopy(),
		},
	}
}

var _ helm.Package = &SLIReporterConfig{}

func (t *SLIReporter) Name() string {
	return "SLIReporter"
}

// some mutateFuncs are overwrited for migrated appbox.
func (t *SLIReporter) Create(ctx context.Context) deploy.Result {
	account, err := util.GetRootUserAccount(t.Config.ComponentPCAARN)
	if err != nil {
		return deploy.Error(err)
	}

	if t.AKSCluster == nil || t.AKSCluster.Status.AtProvider.OidcIssuerURL == nil {
		// If oidc issuer url is empty, we can not create the following resources
		return deploy.Error(fmt.Errorf("aks oidc issuer url is empty"))
	}

	if _, err := applier.NewApplier(t.Local, mutatefuncs.MutateUpboundIAMRole).
		Expected(t.NewIAMRole(false, account)).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}

	if _, err := applier.NewApplier(t.Local, mutatefuncs.MutateUpboundIAMPolicy).
		Expected(t.NewIAMPolicy(false)).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}

	if _, err := applier.NewApplier(t.Local, mutatefuncs.MutateUpboundIAMRolePolicyAttachment).
		Expected(t.NewIAMRolePolicyAttachment(false)).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}

	// for migrate appbox, the name in AWS_ROLE_ARN used by slireporter equals to anno `cloud.pingcap.com/own-crossplane-name-iamrole-slireporter`
	name := namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix("reporter"))
	t.config.awsRoleArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", account, name)
	if tag := t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnImageTagSLIReporter]; tag != "" {
		t.config.overwriteImageTag = tag
	}
	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}

func (t *SLIReporter) PostCreate(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *SLIReporter) Delete(ctx context.Context) deploy.Result {
	if err := k8s.DeleteObjectCompleted(ctx, t.Local, t.NewIAMRolePolicyAttachment(true)); err != nil {
		return deploy.Error(err)
	}

	if err := k8s.DeleteObjectCompleted(ctx, t.Local, t.NewIAMPolicy(true)); err != nil {
		return deploy.Error(err)
	}

	if err := k8s.DeleteObjectCompleted(ctx, t.Local, t.NewIAMRole(true, "")); err != nil {
		return deploy.Error(err)
	}
	return deploy.Done()
}

func (t *SLIReporter) PostDelete(ctx context.Context) deploy.Result {
	return deploy.Done()
}

func (t *SLIReporter) NewIAMRole(onlyMeta bool, account string) *v1beta1.Role {
	name := namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix("reporter"))
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRoleSLIReporter] != "" {
		name = t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRoleSLIReporter]
	}
	role := &v1beta1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return role
	}

	url := getOIDCIssuerURLWithoutScheme(ptr.Deref(t.AKSCluster.Status.AtProvider.OidcIssuerURL, ""))
	role.Spec = v1beta1.RoleSpec{
		ForProvider: v1beta1.RoleParameters{
			AssumeRolePolicy: ptr.To(fmt.Sprintf(assumeRolePolicyDocumentTemplate,
				account,
				url, url,
				t.config.GetNamespace(),
				"reporter-sli-reporter",
			)),
			Description: ptr.To(fmt.Sprintf("role for sli-reporter in aks cluster %s", name)),
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.GlobalCrossplaneProviderConfig},
		},
	}

	return role
}

func (t *SLIReporter) NewIAMPolicy(onlyMeta bool) *v1beta1.Policy {
	name := namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix("reporter"))
	// iampolicy has same name as iamrole
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRoleSLIReporter] != "" {
		name = t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRoleSLIReporter]
	}
	policy := &v1beta1.Policy{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return policy
	}
	policy.Spec = v1beta1.PolicySpec{
		ForProvider: v1beta1.PolicyParameters{
			Policy:      ptr.To(reporterPolicyDocument),
			Description: ptr.To(fmt.Sprintf("policy for sli-reporter in aks cluster %s", name)),
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.GlobalCrossplaneProviderConfig},
		},
	}

	return policy

}

func (t *SLIReporter) NewIAMRolePolicyAttachment(onlyMeta bool) *v1beta1.RolePolicyAttachment {
	name := namer.UniqueIdentifier(t.APPBox.ObjectMeta, namer.WithPrefix("reporter"))
	// iamrolepolicyattachment has same name as iamrole
	if t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRoleSLIReporter] != "" {
		name = t.APPBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRoleSLIReporter]
	}
	rp := &v1beta1.RolePolicyAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return rp
	}

	rp.Spec = v1beta1.RolePolicyAttachmentSpec{
		ForProvider: v1beta1.RolePolicyAttachmentParameters{
			RoleRef: &xpv1.Reference{
				Name: name,
			},
			PolicyArnRef: &xpv1.Reference{
				Name: name,
			},
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.GlobalCrossplaneProviderConfig},
		},
	}

	return rp
}
