package alicloud

import (
	"context"

	aliopenapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
	ramclient "github.com/alibabacloud-go/ram-20150501/v2/client"
	stsclient "github.com/alibabacloud-go/sts-20150401/v2/client"
	util "github.com/alibabacloud-go/tea-utils/v2/service"
	"github.com/alibabacloud-go/tea/tea"
	"github.com/tidbcloud/observability/pkg/common/metrics"
)

const (
	ramClientLabel                  = "ram-client"
	errCreateRoleFailed             = "create new iam role failed"
	errGetRoleFailed                = "get iam role failed"
	errGetPolicyFailed              = "get role policy failed"
	errListRolePoliciesFailed       = "list role policies failed"
	errAttachPolicyFailed           = "attach role policy failed"
	errDetachPolicyFailed           = "detach role policy failed"
	errDeleteRoleFailed             = "delete role failed"
	errUpdateAssumeRolePolicyFailed = "update assume role policy failed"
)

var roleCache = make(map[string]*aliopenapi.Config)

type IAMClient struct {
	ramCli RAMClient
	stsCli StsClient
	config *aliopenapi.Config
}

func NewIAMClient(config *aliopenapi.Config) *IAMClient {
	ramCli, _ := ramclient.NewClient(config)
	stsCli, _ := stsclient.NewClient(config)
	return &IAMClient{
		ramCli: ramCli,
		stsCli: stsCli,
		config: config,
	}
}

func (c *IAMClient) AssumeRole(ctx context.Context, roleArn string) *IAMClient {
	if cachedConfig, ok := roleCache[roleArn]; ok {
		return NewIAMClient(cachedConfig)
	}

	assumeReq := &stsclient.AssumeRoleRequest{
		RoleArn:         tea.String(roleArn),
		RoleSessionName: tea.String("observability-session"),
	}
	resp, _ := c.stsCli.AssumeRoleWithOptions(assumeReq, &util.RuntimeOptions{})

	newConfig := &aliopenapi.Config{
		AccessKeyId:     resp.Body.Credentials.AccessKeyId,
		AccessKeySecret: resp.Body.Credentials.AccessKeySecret,
		SecurityToken:   resp.Body.Credentials.SecurityToken,
		RegionId:        c.config.RegionId,
	}

	roleCache[roleArn] = newConfig
	return NewIAMClient(newConfig)
}

func (c *IAMClient) CreateRole(ctx context.Context, name string, trustDoc string) error {
	req := &ramclient.CreateRoleRequest{
		RoleName:                 tea.String(name),
		AssumeRolePolicyDocument: tea.String(trustDoc),
	}

	_, err := c.ramCli.CreateRoleWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "CreateRole", name, errCreateRoleFailed)
	}
	return err
}

func (c *IAMClient) GetRole(ctx context.Context, name string) (*ramclient.GetRoleResponse, bool, error) {
	req := &ramclient.GetRoleRequest{
		RoleName: tea.String(name),
	}

	resp, err := c.ramCli.GetRoleWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		if IsEntityNotExist(err) {
			return nil, false, nil
		}
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "GetRole", name, errGetRoleFailed)
		return nil, false, err
	}
	return resp, true, nil
}

func (c *IAMClient) ListRolePolicies(ctx context.Context, name string, marker *string) (*ramclient.ListPoliciesForRoleResponse, error) {
	req := &ramclient.ListPoliciesForRoleRequest{
		RoleName: tea.String(name),
	}

	resp, err := c.ramCli.ListPoliciesForRoleWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "ListPoliciesForRole", name, errListRolePoliciesFailed)
	}
	return resp, err
}

func (c *IAMClient) GetCustomPolicyDocument(ctx context.Context, policyName string) (string, bool, error) {
	req := &ramclient.GetPolicyRequest{
		PolicyName: tea.String(policyName),
		PolicyType: tea.String("Custom"),
	}

	resp, err := c.ramCli.GetPolicyWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		if IsPolicyNotExist(err) {
			return "", false, nil
		}
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "GetPolicy", policyName, errGetPolicyFailed)
		return "", false, err
	}
	if resp == nil || resp.Body == nil || resp.Body.DefaultPolicyVersion == nil || resp.Body.DefaultPolicyVersion.PolicyDocument == nil {
		return "", false, nil
	}
	return *resp.Body.DefaultPolicyVersion.PolicyDocument, true, nil
}

func (c *IAMClient) CreateRolePolicy(ctx context.Context, name string, policyName string, policyDoc string) error {
	createPolicyReq := &ramclient.CreatePolicyRequest{
		PolicyName:     tea.String(policyName),
		PolicyDocument: tea.String(policyDoc),
	}
	if _, err := c.ramCli.CreatePolicyWithOptions(createPolicyReq, &util.RuntimeOptions{}); err != nil {
		if !IsPolicyAlreadyExists(err) {
			return err
		}
		if err := c.UpdateRolePolicy(ctx, name, policyName, policyDoc); err != nil {
			return err
		}
	}

	attachReq := &ramclient.AttachPolicyToRoleRequest{
		PolicyType: tea.String("Custom"),
		PolicyName: tea.String(policyName),
		RoleName:   tea.String(name),
	}

	_, err := c.ramCli.AttachPolicyToRoleWithOptions(attachReq, &util.RuntimeOptions{})
	if err != nil {
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "AttachPolicyToRole", name, errAttachPolicyFailed)
	}
	return err
}

func (c *IAMClient) UpdateRolePolicy(ctx context.Context, name string, policyName string, policyDoc string) error {
	asDefault := true
	strategy := "DeleteOldestNonDefaultVersionWhenLimitExceeded"
	createPolicyVersionReq := &ramclient.CreatePolicyVersionRequest{
		PolicyName:     tea.String(policyName),
		PolicyDocument: tea.String(policyDoc),
		RotateStrategy: &strategy,
		SetAsDefault:   &asDefault,
	}
	if _, err := c.ramCli.CreatePolicyVersionWithOptions(createPolicyVersionReq, &util.RuntimeOptions{}); err != nil {
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "UpdateRolePolicy", name, errAttachPolicyFailed)
		return err
	}

	return nil
}

func (c *IAMClient) DeleteRolePolicy(ctx context.Context, name string, policyName string) error {
	detachReq := &ramclient.DetachPolicyFromRoleRequest{
		PolicyType: tea.String("Custom"),
		PolicyName: tea.String(policyName),
		RoleName:   tea.String(name),
	}

	if _, err := c.ramCli.DetachPolicyFromRoleWithOptions(detachReq, &util.RuntimeOptions{}); err != nil {
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "DetachPolicyFromRole", name, errDetachPolicyFailed)
		return err
	}

	deletePolicyReq := &ramclient.DeletePolicyRequest{
		PolicyName: tea.String(policyName),
	}
	_, err := c.ramCli.DeletePolicyWithOptions(deletePolicyReq, &util.RuntimeOptions{})
	return err
}

func (c *IAMClient) DeleteRole(ctx context.Context, name string) error {
	req := &ramclient.DeleteRoleRequest{
		RoleName: tea.String(name),
	}

	_, err := c.ramCli.DeleteRoleWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		if IsEntityNotExist(err) {
			return nil
		}
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "DeleteRole", name, errDeleteRoleFailed)
	}
	return err
}

func (c *IAMClient) UpdateAssumeRolePolicy(ctx context.Context, name string, assumeDocument string) error {
	req := &ramclient.UpdateRoleRequest{
		RoleName:                    tea.String(name),
		NewAssumeRolePolicyDocument: tea.String(assumeDocument),
	}

	_, err := c.ramCli.UpdateRoleWithOptions(req, &util.RuntimeOptions{})
	if err != nil {
		metrics.HandleAliCloudAPIRequestMetrics(*c.config.RegionId, ramClientLabel, "UpdateRole", name, errUpdateAssumeRolePolicyFailed)
	}
	return err
}

func IsEntityNotExist(err error) bool {
	if teaErr, ok := err.(*tea.SDKError); ok {
		if teaErr.Code != nil && *teaErr.Code == "EntityNotExist.Role" {
			return true
		}
	}
	return false
}

func IsPolicyAlreadyExists(err error) bool {
	if teaErr, ok := err.(*tea.SDKError); ok {
		if teaErr.Code != nil && *teaErr.Code == "EntityAlreadyExists.Policy" {
			return true
		}
	}
	return false
}

func IsPolicyNotExist(err error) bool {
	if teaErr, ok := err.(*tea.SDKError); ok {
		if teaErr.Code != nil && *teaErr.Code == "EntityNotExist.Policy" {
			return true
		}
	}
	return false
}
