package gke

import (
	"context"
	"fmt"

	"helm.sh/helm/v3/pkg/chart"
	"helm.sh/helm/v3/pkg/chart/loader"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	"k8s.io/utils/pointer"

	"github.com/tidbcloud/infra-provider/pkg/applier"
	"github.com/tidbcloud/infra-provider/pkg/applier/mutatefuncs"
	"github.com/tidbcloud/infra-provider/pkg/util"

	"github.com/crossplane-contrib/provider-aws/apis/iam/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/eks-provider/controllers/network/task"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/helm"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

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

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

type SLIReporterConfig struct {
	helmChartPath string
	image         config.ImageConfig

	awsRegion                string
	awsDeliveryStreamName    string
	awsDeliveryStreamRoleArn string
	awsRoleArn               string
}

func (c *SLIReporterConfig) GetNamespace() string {
	return task.AdminNamespace
}

func (c *SLIReporterConfig) GetName() string {
	return "reporter"
}

func (c *SLIReporterConfig) GetCRDs() ([]*apiextensionsv1.CustomResourceDefinition, error) {
	return nil, nil
}

func (c *SLIReporterConfig) ToHelmChart() (result *chart.Chart, err error) {
	result, err = loader.Load(c.helmChartPath)
	if err != nil {
		return nil, fmt.Errorf("failed to load helm chart %q: %w", c.helmChartPath, err)
	}
	return
}

func (c *SLIReporterConfig) ToHelmValues() map[string]interface{} {
	return map[string]interface{}{
		"replicaCount": 1,
		"image": map[string]string{
			"repository": c.image.Repo,
			"tag":        c.image.Tag,
		},
		"aws": map[string]string{
			"region":                c.awsRegion,
			"deliveryStreamName":    c.awsDeliveryStreamName,
			"deliveryStreamRoleArn": c.awsDeliveryStreamRoleArn,
			"roleArn":               c.awsRoleArn,
		},
	}
}

func NewReporter(ctx *task.Context) *Reporter {
	return &Reporter{
		BaseTask: &task.BaseTask{Context: ctx},
		config: &SLIReporterConfig{
			helmChartPath:            ctx.Config.Network.SLIReporter.ChartPath,
			image:                    ctx.Config.Network.SLIReporter.Image,
			awsRegion:                ctx.Config.Network.SLIReporter.AWSRegion,
			awsDeliveryStreamName:    ctx.Config.Network.SLIReporter.DeliveryStreamName,
			awsDeliveryStreamRoleArn: ctx.Config.Network.SLIReporter.DeliveryStreamRoleArn,
		},
	}
}

var _ helm.Package = &SLIReporterConfig{}

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

func (t *Reporter) Create(ctx context.Context) deploy.Result {
	account, err := util.GetRootUserAccount(t.Config.FullManage.ComponentPCAARN)
	if err != nil {
		return deploy.Error(err)
	}
	if _, err := applier.NewApplier(t.Local, mutatefuncs.MutateIAMRole).
		Expected(t.NewIAMRole(false, account)).
		Apply(ctx); err != nil {
		return deploy.Error(err)
	}

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

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

	name := namer.UniqueIdentifier(t.Network.ObjectMeta, namer.WithPrefix("reporter"))
	t.config.awsRoleArn = fmt.Sprintf("arn:aws:iam::%s:role/%s", account, name)
	return deploy.ErrorOrDone(helm.Apply(ctx, t.Remote, t.config))
}

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

func (t *Reporter) Delete(ctx context.Context) deploy.Result {
	if err := t.Local.Delete(ctx, t.NewIAMRolePolicyAttachment(true)); err != nil {
		if !errors.IsNotFound(err) {
			return deploy.Error(err)
		}
	}
	if err := t.Local.Delete(ctx, t.NewIAMPolicy(true)); err != nil {
		if !errors.IsNotFound(err) {
			return deploy.Error(err)
		}
	}
	if err := t.Local.Delete(ctx, t.NewIAMRole(true, "")); err != nil {
		if !errors.IsNotFound(err) {
			return deploy.Error(err)
		}
	}

	return deploy.Done()
}

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

func (t *Reporter) NewIAMRole(onlyMeta bool, account string) *v1beta1.Role {
	name := namer.UniqueIdentifier(t.Network.ObjectMeta, namer.WithPrefix("reporter"))
	role := &v1beta1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return role
	}

	url := getURLWithoutScheme(t.GKENetworkSpec.ProjectID, t.Network.Spec.Region, namer.LegacyBuildClusterNameByNetwork(t.Network))
	role.Spec = v1beta1.RoleSpec{
		ForProvider: v1beta1.RoleParameters{
			AssumeRolePolicyDocument: fmt.Sprintf(assumeRolePolicyDocumentTemplate,
				account,
				url, url,
				t.config.GetNamespace(),
				"reporter-sli-reporter",
			),
			Description: pointer.String(fmt.Sprintf("role for sli-reporter in gke cluster %s", name)),
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.ControlplaneAWSProviderConfig},
		},
	}

	return role
}

func (t *Reporter) NewIAMPolicy(onlyMeta bool) *v1beta1.Policy {
	name := namer.UniqueIdentifier(t.Network.ObjectMeta, namer.WithPrefix("reporter"))
	policy := &v1beta1.Policy{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return policy
	}
	policy.Spec = v1beta1.PolicySpec{
		ForProvider: v1beta1.PolicyParameters{
			Name:        name + "-reporter-policy",
			Document:    reporterPolicyDocument,
			Description: pointer.String(fmt.Sprintf("policy for sli-reporter in gke cluster %s", name)),
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.ControlplaneAWSProviderConfig},
		},
	}

	return policy

}

func (t *Reporter) NewIAMRolePolicyAttachment(onlyMeta bool) *v1beta1.RolePolicyAttachment {
	name := namer.UniqueIdentifier(t.Network.ObjectMeta, namer.WithPrefix("reporter"))
	rp := &v1beta1.RolePolicyAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name: name,
		},
	}
	if onlyMeta {
		return rp

	}

	rp.Spec = v1beta1.RolePolicyAttachmentSpec{
		ForProvider: v1beta1.RolePolicyAttachmentParameters{
			RoleNameRef: &xpv1.Reference{
				Name: name,
			},
			PolicyARNRef: &xpv1.Reference{
				Name: name,
			},
		},
		ResourceSpec: xpv1.ResourceSpec{
			ProviderConfigReference: &xpv1.Reference{Name: t.Config.ControlplaneAWSProviderConfig},
		},
	}

	return rp
}
