package alicloud

import (
	"context"
	"testing"

	"github.com/tidbcloud/observability/pkg/common/errors"

	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"
	"github.com/alibabacloud-go/tea/tea"

	. "github.com/onsi/gomega"
	"github.com/stretchr/testify/suite"
	mocks "github.com/tidbcloud/observability/test/mockresourceworker/providers/alicloud"
	"go.uber.org/mock/gomock"
)

type IAMClientTestSuite struct {
	suite.Suite
	mockCtrl    *gomock.Controller
	mockRAM     *mocks.MockRAMClient
	mockSTS     *mocks.MockStsClient
	iamClient   *IAMClient
	originalCfg *aliopenapi.Config
}

func newMockIAM(cfg *aliopenapi.Config, mock1 RAMClient, mock2 StsClient) *IAMClient {
	return &IAMClient{
		ramCli: mock1,
		stsCli: mock2,
		config: cfg,
	}
}

func TestIAMClientTestSuite(t *testing.T) {
	suite.Run(t, new(IAMClientTestSuite))
}

func (s *IAMClientTestSuite) SetupTest() {
	s.mockCtrl = gomock.NewController(s.T())
	s.mockRAM = mocks.NewMockRAMClient(s.mockCtrl)
	s.mockSTS = mocks.NewMockStsClient(s.mockCtrl)
	s.originalCfg = &aliopenapi.Config{RegionId: tea.String("ap-northeast-1")}
	s.iamClient = newMockIAM(s.originalCfg, s.mockRAM, s.mockSTS)
	// Clear cache before each test
	roleCache = make(map[string]*aliopenapi.Config)
}

func (s *IAMClientTestSuite) TearDownTest() {
	s.mockCtrl.Finish()
}

// TestAssumeRole_CacheHit verifies cache hit scenario
func (s *IAMClientTestSuite) TestAssumeRole_CacheHit() {
	g := NewWithT(s.T())
	roleArn := "test-role-arn"
	cachedConfig := &aliopenapi.Config{RegionId: tea.String("cn-hangzhou")}
	roleCache[roleArn] = cachedConfig

	result := s.iamClient.AssumeRole(context.Background(), roleArn)
	g.Expect(result.config).To(Equal(cachedConfig))
}

// TestAssumeRole_CacheMiss verifies new credentials generation
func (s *IAMClientTestSuite) TestAssumeRole_CacheMiss() {
	g := NewWithT(s.T())
	roleArn := "test-role-arn"

	// Mock STS response
	mockResponse := &stsclient.AssumeRoleResponse{
		Body: &stsclient.AssumeRoleResponseBody{
			Credentials: &stsclient.AssumeRoleResponseBodyCredentials{
				AccessKeyId:     tea.String("new-key-id"),
				AccessKeySecret: tea.String("new-key-secret"),
				SecurityToken:   tea.String("new-token"),
			},
		},
	}
	s.mockSTS.EXPECT().AssumeRoleWithOptions(gomock.Any(), gomock.Any()).Return(mockResponse, nil)

	result := s.iamClient.AssumeRole(context.Background(), roleArn)
	g.Expect(*result.config.AccessKeyId).To(Equal("new-key-id"))
	g.Expect(roleCache).To(HaveKeyWithValue(roleArn, result.config))
}

// TestCreateRole_Success verifies successful role creation
func (s *IAMClientTestSuite) TestCreateRole_Success() {
	g := NewWithT(s.T())
	roleName := "test-role"

	s.mockRAM.EXPECT().CreateRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, nil)

	err := s.iamClient.CreateRole(context.Background(), roleName, "trust-doc")
	g.Expect(err).ToNot(HaveOccurred())

	s.mockRAM.EXPECT().CreateRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, errors.New(errors.UnrecognizedError, ""))
	err = s.iamClient.CreateRole(context.Background(), roleName, "trust-doc")
	g.Expect(err).Error()
}

// TestGetRole_Exists verifies existing role retrieval
func (s *IAMClientTestSuite) TestGetRole_Exists() {
	g := NewWithT(s.T())
	roleName := "existing-role"
	mockResponse := &ramclient.GetRoleResponse{}

	s.mockRAM.EXPECT().GetRoleWithOptions(gomock.Any(), gomock.Any()).Return(mockResponse, nil)

	resp, exists, err := s.iamClient.GetRole(context.Background(), roleName)
	g.Expect(err).ToNot(HaveOccurred())
	g.Expect(exists).To(BeTrue())
	g.Expect(resp).To(Equal(mockResponse))
}

// TestGetRole_NotExists verifies non-existing role handling
func (s *IAMClientTestSuite) TestGetRole_NotExists() {
	g := NewWithT(s.T())
	roleName := "non-existing-role"
	mockError := &tea.SDKError{Code: tea.String("EntityNotExist.Role")}

	s.mockRAM.EXPECT().GetRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	_, exists, err := s.iamClient.GetRole(context.Background(), roleName)
	g.Expect(err).ToNot(HaveOccurred())
	g.Expect(exists).To(BeFalse())

	mockError = &tea.SDKError{Code: tea.String("test")}
	s.mockRAM.EXPECT().GetRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)
	_, exists, err = s.iamClient.GetRole(context.Background(), roleName)
	g.Expect(err).Error()
	g.Expect(exists).To(BeFalse())
}

func (s *IAMClientTestSuite) TestListRolePolicies_Success() {
	g := NewWithT(s.T())
	roleName := "test-role"
	mockResponse := &ramclient.ListPoliciesForRoleResponse{}

	s.mockRAM.EXPECT().ListPoliciesForRoleWithOptions(gomock.Any(), gomock.Any()).Return(mockResponse, nil)

	resp, err := s.iamClient.ListRolePolicies(context.Background(), roleName, nil)
	g.Expect(err).ToNot(HaveOccurred())
	g.Expect(resp).To(Equal(mockResponse))
}

func (s *IAMClientTestSuite) TestListRolePolicies_APIError() {
	g := NewWithT(s.T())
	roleName := "test-role"
	mockError := &tea.SDKError{Message: tea.String("API Error")}

	s.mockRAM.EXPECT().ListPoliciesForRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	_, err := s.iamClient.ListRolePolicies(context.Background(), roleName, nil)
	g.Expect(err).To(HaveOccurred())
}

func (s *IAMClientTestSuite) TestCreateRolePolicy_Success() {
	g := NewWithT(s.T())
	roleName := "test-role"
	policyName := "test-policy"
	policyDoc := `{"Statement": []}`

	createCall := s.mockRAM.EXPECT().CreatePolicyWithOptions(gomock.Any(), gomock.Any()).
		Return(&ramclient.CreatePolicyResponse{}, nil)
	s.mockRAM.EXPECT().AttachPolicyToRoleWithOptions(gomock.Any(), gomock.Any()).
		After(createCall).Return(&ramclient.AttachPolicyToRoleResponse{}, nil)

	err := s.iamClient.CreateRolePolicy(context.Background(), roleName, policyName, policyDoc)
	g.Expect(err).ToNot(HaveOccurred())
}

func (s *IAMClientTestSuite) TestCreateRolePolicy_CreateFailure() {
	g := NewWithT(s.T())
	roleName := "test-role"
	policyName := "test-policy"
	mockError := &tea.SDKError{Message: tea.String("Create Error")}

	s.mockRAM.EXPECT().CreatePolicyWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	err := s.iamClient.CreateRolePolicy(context.Background(), roleName, policyName, "")
	g.Expect(err).To(HaveOccurred())
}

func (s *IAMClientTestSuite) TestCreateRolePolicy_AttachesExistingPolicy() {
	g := NewWithT(s.T())
	roleName := "test-role"
	policyName := "test-policy"
	policyDoc := `{"Statement": []}`
	existsErr := &tea.SDKError{Code: tea.String("EntityAlreadyExists.Policy")}

	createCall := s.mockRAM.EXPECT().CreatePolicyWithOptions(gomock.Any(), gomock.Any()).
		Return(nil, existsErr)
	updateCall := s.mockRAM.EXPECT().CreatePolicyVersionWithOptions(gomock.Any(), gomock.Any()).
		After(createCall).Return(&ramclient.CreatePolicyVersionResponse{}, nil)
	s.mockRAM.EXPECT().AttachPolicyToRoleWithOptions(gomock.Any(), gomock.Any()).
		After(updateCall).Return(&ramclient.AttachPolicyToRoleResponse{}, nil)

	err := s.iamClient.CreateRolePolicy(context.Background(), roleName, policyName, policyDoc)
	g.Expect(err).ToNot(HaveOccurred())
}

func (s *IAMClientTestSuite) TestGetCustomPolicyDocument_Success() {
	g := NewWithT(s.T())
	policyName := "test-policy"
	policyDocument := `{"Statement":[]}`

	s.mockRAM.EXPECT().GetPolicyWithOptions(gomock.Any(), gomock.Any()).Return(&ramclient.GetPolicyResponse{
		Body: &ramclient.GetPolicyResponseBody{
			DefaultPolicyVersion: &ramclient.GetPolicyResponseBodyDefaultPolicyVersion{
				PolicyDocument: tea.String(policyDocument),
			},
		},
	}, nil)

	got, exists, err := s.iamClient.GetCustomPolicyDocument(context.Background(), policyName)
	g.Expect(err).ToNot(HaveOccurred())
	g.Expect(exists).To(BeTrue())
	g.Expect(got).To(Equal(policyDocument))
}

func (s *IAMClientTestSuite) TestGetCustomPolicyDocument_NotFound() {
	g := NewWithT(s.T())
	policyName := "test-policy"
	notFoundErr := &tea.SDKError{Code: tea.String("EntityNotExist.Policy")}

	s.mockRAM.EXPECT().GetPolicyWithOptions(gomock.Any(), gomock.Any()).Return(nil, notFoundErr)

	got, exists, err := s.iamClient.GetCustomPolicyDocument(context.Background(), policyName)
	g.Expect(err).ToNot(HaveOccurred())
	g.Expect(exists).To(BeFalse())
	g.Expect(got).To(Equal(""))
}

func (s *IAMClientTestSuite) TestCreateRolePolicy_AttachFailure() {
	g := NewWithT(s.T())
	roleName := "test-role"
	policyName := "test-policy"
	mockError := &tea.SDKError{Message: tea.String("Attach Error")}

	s.mockRAM.EXPECT().CreatePolicyWithOptions(gomock.Any(), gomock.Any()).Return(&ramclient.CreatePolicyResponse{}, nil)
	s.mockRAM.EXPECT().AttachPolicyToRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	err := s.iamClient.CreateRolePolicy(context.Background(), roleName, policyName, "")
	g.Expect(err).To(HaveOccurred())
}

// TestDeleteRole_Success verifies successful role deletion
func (s *IAMClientTestSuite) TestDeleteRole_Success() {
	g := NewWithT(s.T())
	roleName := "test-role"

	s.mockRAM.EXPECT().DeleteRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, nil)

	err := s.iamClient.DeleteRole(context.Background(), roleName)
	g.Expect(err).ToNot(HaveOccurred())
}

// TestUpdateAssumeRolePolicy_Failure verifies error handling
func (s *IAMClientTestSuite) TestUpdateAssumeRolePolicy_Failure() {
	g := NewWithT(s.T())
	roleName := "test-role"
	mockError := &tea.SDKError{Message: tea.String("update failed")}

	s.mockRAM.EXPECT().UpdateRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	err := s.iamClient.UpdateAssumeRolePolicy(context.Background(), roleName, "new-doc")
	g.Expect(err).To(HaveOccurred())
}

// TestIsEntityNotExist verifies error type detection
func (s *IAMClientTestSuite) TestIsEntityNotExist() {
	g := NewWithT(s.T())

	// Test with NoSuchEntity error
	teaError := &tea.SDKError{Code: tea.String("EntityNotExist.Role")}
	g.Expect(IsEntityNotExist(teaError)).To(BeTrue())

	// Test with other error
	otherError := &tea.SDKError{Code: tea.String("OtherError")}
	g.Expect(IsEntityNotExist(otherError)).To(BeFalse())
}

func (s *IAMClientTestSuite) TestIsPolicyAlreadyExists() {
	g := NewWithT(s.T())

	teaError := &tea.SDKError{Code: tea.String("EntityAlreadyExists.Policy")}
	g.Expect(IsPolicyAlreadyExists(teaError)).To(BeTrue())

	otherError := &tea.SDKError{Code: tea.String("OtherError")}
	g.Expect(IsPolicyAlreadyExists(otherError)).To(BeFalse())
}

func (s *IAMClientTestSuite) TestIsPolicyNotExist() {
	g := NewWithT(s.T())

	teaError := &tea.SDKError{Code: tea.String("EntityNotExist.Policy")}
	g.Expect(IsPolicyNotExist(teaError)).To(BeTrue())

	otherError := &tea.SDKError{Code: tea.String("OtherError")}
	g.Expect(IsPolicyNotExist(otherError)).To(BeFalse())
}

// TestPutRolePolicy_AttachFailure verifies policy attachment failure
func (s *IAMClientTestSuite) TestPutRolePolicy_AttachFailure() {
	g := NewWithT(s.T())
	roleName := "test-role"
	policyName := "test-policy"
	mockError := &tea.SDKError{Message: tea.String("attach failed")}

	// Mock CreatePolicyVersion failure
	s.mockRAM.EXPECT().CreatePolicyVersionWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	err := s.iamClient.UpdateRolePolicy(context.Background(), roleName, policyName, "policy-doc")
	g.Expect(err).To(HaveOccurred())
}

// TestDeleteRolePolicy_DetachFailure verifies policy detach failure
func (s *IAMClientTestSuite) TestDeleteRolePolicy_DetachFailure() {
	g := NewWithT(s.T())
	roleName := "test-role"
	policyName := "test-policy"
	mockError := &tea.SDKError{Message: tea.String("detach failed")}

	s.mockRAM.EXPECT().DetachPolicyFromRoleWithOptions(gomock.Any(), gomock.Any()).Return(nil, mockError)

	err := s.iamClient.DeleteRolePolicy(context.Background(), roleName, policyName)
	g.Expect(err).To(HaveOccurred())
}

//func Test_sdk(t *testing.T) {
//	ststoken, err := getStSToken()
//	if err != nil {
//		t.Errorf("getStSToken failed: %v", err)
//	}
//	stsConfig := new(credentials.Config).
//		SetType("sts").
//		SetAccessKeyId(ststoken.AccessKeyId).
//		SetAccessKeySecret(ststoken.AccessKeySecret).
//		SetSecurityToken(ststoken.StsToken)
//	stsCredential, err := credentials.NewCredential(stsConfig)
//	region := "ap-southeast-1"
//	config := &aliopenapi.Config{
//		Credential: stsCredential,
//		RegionId:   &region,
//	}
//
//	c := NewIAMClient(config)
//	_, exist, err := c.GetRole(context.TODO(), "test")
//	assert.False(t, exist)
//}
