package eks

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	iamv1beta1 "github.com/crossplane-contrib/provider-aws/apis/iam/v1beta1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/stretchr/testify/require"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/types"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/client/interceptor"

	"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"
)

func newNodeRoleTestScheme() *runtime.Scheme {
	s := scheme.Scheme
	utilruntime.Must(iamv1beta1.SchemeBuilder.AddToScheme(s))
	_ = v1alpha1.AddToScheme(s)
	return s
}

func newNodeRoleTestAppBox(nodeRoleName string) *v1alpha1.APPBox {
	appBox := &v1alpha1.APPBox{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "test-appbox",
			Namespace: "default",
			UID:       types.UID("test-uid"),
			Labels: map[string]string{
				v1alpha1.TagProjectKey: "test-project",
				v1alpha1.IsLegacyKey:   "true",
			},
		},
	}

	if nodeRoleName != "" {
		if appBox.Annotations == nil {
			appBox.Annotations = make(map[string]string)
		}
		appBox.Annotations[v1alpha1.AnnotationKeyOwnCrossplaneNameIAMRole] = nodeRoleName
	}

	return appBox
}

func newTestPolicy(name, nodeRoleName, policyType string) *iamv1beta1.Policy {
	return &iamv1beta1.Policy{
		ObjectMeta: metav1.ObjectMeta{
			Name:   policyName(name, policyType),
			Labels: map[string]string{RoleRefLabelKey: nodeRoleName},
		},
		Spec: iamv1beta1.PolicySpec{
			ForProvider: iamv1beta1.PolicyParameters{
				Name:     policyName(name, policyType),
				Document: "{}",
			},
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: "test-provider"},
			},
		},
	}
}

func newNodeRoleTestTaskContext(c client.Client, appBox *v1alpha1.APPBox, policyFile string) (*task.Context, func()) {
	var cleanup func() = func() {} // Default empty cleanup function
	actualPolicyFile := policyFile

	// Create temporary file only when needed
	if policyFile == "valid-policy-file" {
		tempDir, err := os.MkdirTemp("", "test-policy")
		if err != nil {
			panic(err)
		}

		policyPath := filepath.Join(tempDir, "policy.json")
		err = os.WriteFile(policyPath, []byte(`{"Version":"2012-10-17","Statement":[]}`), 0644)
		if err != nil {
			panic(err)
		}

		actualPolicyFile = policyPath
		cleanup = func() { os.RemoveAll(tempDir) }
	}

	cfg := config.RootConfig{
		ControllerConfig: config.ControllerConfig{
			APPBoxConfig: config.APPBoxConfig{
				AdminNodeRoleConfig: config.AdminNodeRoleConfig{
					AWSLBControllerPolicyFile: actualPolicyFile,
				},
			},
		},
	}

	return &task.Context{
		Local:  c,
		APPBox: appBox,
		VPC: &v1alpha1.VPC{
			Spec: v1alpha1.VPCSpec{
				AccountRef: &v1alpha1.AccountReference{
					ProviderConfigName: "test-provider",
				},
			},
		},
		Config: cfg,
	}, cleanup
}

func TestNodeRole_updateLegacyNodeRolePolicy(t *testing.T) {
	tests := []struct {
		name             string
		nodeRoleName     string
		existingPolicies []*iamv1beta1.Policy
		policyFile       string
		mockError        string
		expectError      bool
	}{
		{
			name:         "empty_node_role_name",
			nodeRoleName: "",
			expectError:  true,
		},
		{
			name:         "list_policies_fails",
			nodeRoleName: "test-role",
			mockError:    "list",
			expectError:  true,
		},
		{
			name:         "no_matching_policy",
			nodeRoleName: "test-role",
			existingPolicies: []*iamv1beta1.Policy{
				newTestPolicy("test-role", "test-role", "other-policy"),
			},
			policyFile:  "valid-policy-file",
			expectError: false,
		},
		{
			name:         "policy_file_read_fails",
			nodeRoleName: "test-role",
			existingPolicies: []*iamv1beta1.Policy{
				newTestPolicy("test-role", "test-role", awsLBControllerPolicyName),
			},
			policyFile:  "",
			expectError: true,
		},
		{
			name:         "policy_update_fails",
			nodeRoleName: "test-role",
			existingPolicies: []*iamv1beta1.Policy{
				newTestPolicy("test-role", "test-role", awsLBControllerPolicyName),
			},
			policyFile:  "valid-policy-file",
			mockError:   "update",
			expectError: true,
		},
		{
			name:         "success",
			nodeRoleName: "test-role",
			existingPolicies: []*iamv1beta1.Policy{
				newTestPolicy("test-role", "test-role", awsLBControllerPolicyName),
			},
			policyFile:  "valid-policy-file",
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Prepare test data
			appBox := newNodeRoleTestAppBox(tt.nodeRoleName)
			var objs []client.Object
			for _, policy := range tt.existingPolicies {
				objs = append(objs, policy)
			}

			// Create client with error mocking as needed
			clientBuilder := fake.NewClientBuilder().WithScheme(newNodeRoleTestScheme()).WithObjects(objs...)

			if tt.mockError != "" {
				var interceptorFuncs interceptor.Funcs
				switch tt.mockError {
				case "list":
					interceptorFuncs.List = func(ctx context.Context, client client.WithWatch, list client.ObjectList, opts ...client.ListOption) error {
						return fmt.Errorf("failed to list policies")
					}
				case "update":
					interceptorFuncs.Update = func(ctx context.Context, client client.WithWatch, obj client.Object, opts ...client.UpdateOption) error {
						if _, ok := obj.(*iamv1beta1.Policy); ok {
							return fmt.Errorf("update failed")
						}
						return nil
					}
				}
				clientBuilder = clientBuilder.WithInterceptorFuncs(interceptorFuncs)
			}

			c := clientBuilder.Build()

			// Create task context
			taskCtx, cleanup := newNodeRoleTestTaskContext(c, appBox, tt.policyFile)
			defer cleanup()

			// Create NodeRole task
			nodeRole := &NodeRole{
				BaseTask: &task.BaseTask{Context: taskCtx},
			}

			// Execute test
			result := nodeRole.updateLegacyNodeRolePolicy(context.Background(), taskCtx.Config.APPBoxConfig.AdminNodeRoleConfig)

			// Verify results
			if tt.expectError {
				require.True(t, result.Error() != nil, "Expected error but got: %+v", result)
			} else {
				require.True(t, result.IsDoneState(), "Expected Done but got: %+v", result)
			}
		})
	}
}
