package keyspacerole

import (
	"context"
	"fmt"
	"strings"

	ramv1alpha1 "github.com/crossplane-contrib/provider-upjet-alibabacloud/apis/ram/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	dmpbv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/k8s"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/ptr"
)

func (p alicloudKeyspaceRoleProvider) apply(ctx context.Context, spec *keyspaceRoleSpec) error {
	// Keyspace roles, policies, and attachments are separate Crossplane resources; retry completes any partial apply.
	if err := p.validateExistingOwners(ctx, spec); err != nil {
		return err
	}
	if err := p.applyRole(ctx, spec); err != nil {
		return bizerr.Wrap(bizerr.K8SClientError, "apply keyspace role error", err)
	}
	if err := p.applyRolePolicy(ctx, spec); err != nil {
		return bizerr.Wrap(bizerr.K8SClientError, "apply keyspace role policy error", err)
	}
	return nil
}

func (p alicloudKeyspaceRoleProvider) validateExistingOwners(ctx context.Context, spec *keyspaceRoleSpec) error {
	if err := validateExistingResourceOwner(ctx, p.local, &ramv1alpha1.Role{}, spec.roleName, spec.dataplaneID); err != nil {
		return err
	}
	if err := validateExistingResourceOwner(ctx, p.local, &ramv1alpha1.Policy{}, spec.rolePolicyName, spec.dataplaneID); err != nil {
		return err
	}
	return validateExistingResourceOwner(ctx, p.local, &ramv1alpha1.RolePolicyAttachment{}, rolePolicyAttachmentName(spec.roleName), spec.dataplaneID)
}

func (p alicloudKeyspaceRoleProvider) applyRole(ctx context.Context, spec *keyspaceRoleSpec) error {
	trustPolicy, err := buildRAMTrustPolicy(spec.accountID, spec.assumeRoleARN, spec.externalID)
	if err != nil {
		return err
	}
	role := &ramv1alpha1.Role{
		ObjectMeta: metav1.ObjectMeta{
			Name:        spec.roleName,
			Labels:      spec.labels,
			Annotations: spec.annotations,
		},
		Spec: ramv1alpha1.RoleSpec{
			ForProvider: ramv1alpha1.RoleParameters{
				Description:              ptr.To(fmt.Sprintf("Keyspace role for keyspace %s", spec.keyspaceName)),
				RoleName:                 ptr.To(spec.roleName),
				AssumeRolePolicyDocument: ptr.To(trustPolicy),
				Tags:                     util.ToMapPointer(spec.cloudTags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: spec.providerConfig},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, p.local, role)
}

func (p alicloudKeyspaceRoleProvider) applyRolePolicy(ctx context.Context, spec *keyspaceRoleSpec) error {
	policyDocument, err := buildOSSPolicy(spec.bucketName, spec.allowedPrefix)
	if err != nil {
		return err
	}
	policy := &ramv1alpha1.Policy{
		ObjectMeta: metav1.ObjectMeta{
			Name:        spec.rolePolicyName,
			Labels:      spec.labels,
			Annotations: spec.annotations,
		},
		Spec: ramv1alpha1.PolicySpec{
			ForProvider: ramv1alpha1.PolicyParameters{
				Description:    ptr.To(fmt.Sprintf("Keyspace role policy for keyspace %s", spec.keyspaceName)),
				PolicyDocument: ptr.To(policyDocument),
				PolicyName:     ptr.To(spec.rolePolicyName),
				Tags:           util.ToMapPointer(spec.cloudTags),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: spec.providerConfig},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
		},
	}
	if err := k8s.Apply(ctx, p.local, policy); err != nil {
		return err
	}

	attachment := &ramv1alpha1.RolePolicyAttachment{
		ObjectMeta: metav1.ObjectMeta{
			Name:        rolePolicyAttachmentName(spec.roleName),
			Labels:      spec.labels,
			Annotations: spec.annotations,
		},
		Spec: ramv1alpha1.RolePolicyAttachmentSpec{
			ForProvider: ramv1alpha1.RolePolicyAttachmentParameters{
				PolicyType:    ptr.To("Custom"),
				PolicyNameRef: &xpv1.Reference{Name: spec.rolePolicyName},
				RoleName:      ptr.To(spec.roleName),
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: spec.providerConfig},
				ManagementPolicies:      controllers.GetResourceManagementPolicies(),
			},
		},
	}
	return k8s.Apply(ctx, p.local, attachment)
}

func (p alicloudKeyspaceRoleProvider) get(ctx context.Context, dataplaneID, keyspaceName, roleName string) (*dmpbv1.KeyspaceRole, error) {
	role := &ramv1alpha1.Role{}
	if err := p.local.Get(ctx, types.NamespacedName{Name: roleName}, role); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, bizerr.New(bizerr.ResourceNotFound, "keyspace role not found")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get keyspace role error", err)
	}
	if err := validateDataplaneOwner(role.Labels, dataplaneID); err != nil {
		return nil, err
	}

	policy := &ramv1alpha1.Policy{}
	if err := p.local.Get(ctx, types.NamespacedName{Name: rolePolicyName(roleName)}, policy); err != nil {
		if apierrors.IsNotFound(err) {
			return keyspaceRoleResponse(dataplaneID, keyspaceName, ramRoleARN(role), role.Labels, role.Annotations, dmpbv1.KeyspaceRole_Creating, "keyspace role policy is not ready"), nil
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get keyspace role policy error", err)
	}

	attachment := &ramv1alpha1.RolePolicyAttachment{}
	if err := p.local.Get(ctx, types.NamespacedName{Name: rolePolicyAttachmentName(roleName)}, attachment); err != nil {
		if apierrors.IsNotFound(err) {
			return keyspaceRoleResponse(dataplaneID, keyspaceName, ramRoleARN(role), role.Labels, role.Annotations, dmpbv1.KeyspaceRole_Creating, "keyspace role policy attachment is not ready"), nil
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get keyspace role policy attachment error", err)
	}

	status, message := keyspaceRoleStatus(ramRoleARN(role), managedResource{
		name:   "keyspace role",
		object: role,
		status: role.Status.ResourceStatus,
	}, managedResource{
		name:   "keyspace role policy",
		object: policy,
		status: policy.Status.ResourceStatus,
	}, managedResource{
		name:   "keyspace role policy attachment",
		object: attachment,
		status: attachment.Status.ResourceStatus,
	})
	return keyspaceRoleResponse(dataplaneID, keyspaceName, ramRoleARN(role), role.Labels, role.Annotations, status, message), nil
}

func (p alicloudKeyspaceRoleProvider) delete(ctx context.Context, dataplaneID, roleName string) error {
	role := &ramv1alpha1.Role{}
	if err := p.local.Get(ctx, types.NamespacedName{Name: roleName}, role); err != nil {
		if !apierrors.IsNotFound(err) {
			return bizerr.Wrap(bizerr.K8SClientError, "get keyspace role error", err)
		}
	} else if err := validateDataplaneOwner(role.Labels, dataplaneID); err != nil {
		return err
	}

	if err := controllers.DeleteUpboundResource(ctx, p.local, &ramv1alpha1.RolePolicyAttachment{
		ObjectMeta: metav1.ObjectMeta{Name: rolePolicyAttachmentName(roleName)},
	}); err != nil {
		return bizerr.Wrap(bizerr.K8SClientError, "delete keyspace role policy attachment error", err)
	}
	if err := controllers.DeleteUpboundResource(ctx, p.local, &ramv1alpha1.Policy{
		ObjectMeta: metav1.ObjectMeta{Name: rolePolicyName(roleName)},
	}); err != nil {
		return bizerr.Wrap(bizerr.K8SClientError, "delete keyspace role policy error", err)
	}
	if err := controllers.DeleteUpboundResource(ctx, p.local, &ramv1alpha1.Role{
		ObjectMeta: metav1.ObjectMeta{Name: roleName},
	}); err != nil {
		return bizerr.Wrap(bizerr.K8SClientError, "delete keyspace role error", err)
	}
	return nil
}

func buildRAMTrustPolicy(accountID, principalARN, externalID string) (string, error) {
	principalARN = strings.ToLower(strings.TrimSpace(principalARN))
	if accountID == "" || principalARN == "" || externalID == "" {
		return "", bizerr.New(bizerr.ParameterInvalid, "account id, principal arn and external id are required")
	}
	policy := map[string]any{
		"Version": "1",
		"Statement": []map[string]any{
			{
				"Effect": "Allow",
				"Principal": map[string][]string{
					"RAM": {
						fmt.Sprintf("acs:ram::%s:root", accountID),
					},
				},
				"Action": "sts:AssumeRole",
				"Condition": map[string]map[string]string{
					"StringEquals": {
						"acs:PrincipalARN": principalARN,
						"sts:ExternalId":   externalID,
					},
				},
			},
		},
	}
	return marshalPolicy(policy)
}

func buildOSSPolicy(bucketName, allowedPrefix string) (string, error) {
	allowedPrefix = normalizeS3Prefix(allowedPrefix)
	if bucketName == "" || allowedPrefix == "" {
		return "", fmt.Errorf("bucket name and allowed prefix are required")
	}
	policy := map[string]any{
		"Version": "1",
		"Statement": []map[string]any{
			{
				"Effect":   "Allow",
				"Action":   "oss:ListObjects",
				"Resource": fmt.Sprintf("acs:oss:*:*:%s", bucketName),
				"Condition": map[string]map[string][]string{
					"StringLike": {
						"oss:Prefix": {
							allowedPrefix + "/*",
						},
					},
				},
			},
			{
				"Effect": "Allow",
				"Action": []string{
					"oss:GetObject",
					"oss:PutObject",
					"oss:DeleteObject",
					"oss:AbortMultipartUpload",
					"oss:ListParts",
				},
				"Resource": fmt.Sprintf("acs:oss:*:*:%s/%s/*", bucketName, allowedPrefix),
			},
		},
	}
	return marshalPolicy(policy)
}

func ramRoleARN(role *ramv1alpha1.Role) string {
	if role.Status.AtProvider.Arn == nil {
		return ""
	}
	return *role.Status.AtProvider.Arn
}
