package iam

import (
	"context"
	"sync"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
	"github.com/grafana/grafana/pkg/infra/log"
	v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
	"github.com/stretchr/testify/require"
)

func TestAfterUserCreate(t *testing.T) {
	var wg sync.WaitGroup

	b := &IdentityAccessManagementAPIBuilder{
		logger:   log.NewNopLogger(),
		zTickets: make(chan bool, 1),
	}

	t.Run("should create zanzana entry for user with Admin role", func(t *testing.T) {
		wg.Add(1)
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "df2p421det1q8c",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		testAdminRole := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-1", req.Namespace)
			require.Len(t, req.Operations, 1)

			op := req.Operations[0]
			require.NotNil(t, op)
			updateOp := op.GetUpdateUserOrgRole()
			require.NotNil(t, updateOp)
			require.Equal(t, "df2p421det1q8c", updateOp.User)
			require.Equal(t, "Admin", updateOp.Role)
			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testAdminRole}
		b.AfterUserCreate(&user, nil)
		wg.Wait()
	})

	t.Run("should create zanzana entry for user with Editor role", func(t *testing.T) {
		wg.Add(1)
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "user123",
				Namespace: "org-2",
			},
			Spec: iamv0.UserSpec{
				Role: "Editor",
			},
		}

		testEditorRole := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-2", req.Namespace)
			require.Len(t, req.Operations, 1)

			op := req.Operations[0]
			require.NotNil(t, op)
			updateOp := op.GetUpdateUserOrgRole()
			require.NotNil(t, updateOp)
			require.Equal(t, "user123", updateOp.User)
			require.Equal(t, "Editor", updateOp.Role)
			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testEditorRole}
		b.AfterUserCreate(&user, nil)
		wg.Wait()
	})

	t.Run("should create zanzana entry for user with Viewer role", func(t *testing.T) {
		wg.Add(1)
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "viewer456",
				Namespace: "org-3",
			},
			Spec: iamv0.UserSpec{
				Role: "Viewer",
			},
		}

		testViewerRole := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-3", req.Namespace)
			require.Len(t, req.Operations, 1)

			op := req.Operations[0]
			require.NotNil(t, op)
			updateOp := op.GetUpdateUserOrgRole()
			require.NotNil(t, updateOp)
			require.Equal(t, "viewer456", updateOp.User)
			require.Equal(t, "Viewer", updateOp.Role)
			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testViewerRole}
		b.AfterUserCreate(&user, nil)
		wg.Wait()
	})

	t.Run("should skip when user has no role", func(t *testing.T) {
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "norole789",
				Namespace: "org-4",
			},
			Spec: iamv0.UserSpec{
				Role: "",
			},
		}

		// Should not call zanzana client
		b.zClient = nil
		b.AfterUserCreate(&user, nil)
		// If we get here without panic, the test passes
	})

	t.Run("should skip when zClient is nil", func(t *testing.T) {
		builder := &IdentityAccessManagementAPIBuilder{
			logger:   log.NewNopLogger(),
			zTickets: make(chan bool, 1),
			zClient:  nil,
		}

		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		// Should return early without calling zanzana
		builder.AfterUserCreate(&user, nil)
		// If we get here without panic, the test passes
	})
}

func TestBeginUserUpdate(t *testing.T) {
	var wg sync.WaitGroup

	b := &IdentityAccessManagementAPIBuilder{
		logger:   log.NewNopLogger(),
		zTickets: make(chan bool, 1),
	}

	t.Run("should update zanzana entry when role changes from Viewer to Admin", func(t *testing.T) {
		wg.Add(1)
		oldUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Viewer",
			},
		}

		newUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		testRoleChange := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-1", req.Namespace)
			require.Len(t, req.Operations, 2)

			// First operation should be UpdateUserOrgRole with new role
			updateOp := req.Operations[0].GetUpdateUserOrgRole()
			require.NotNil(t, updateOp)
			require.Equal(t, "testuser", updateOp.User)
			require.Equal(t, "Admin", updateOp.Role)

			// Second operation should be DeleteUserOrgRole with old role
			deleteOp := req.Operations[1].GetDeleteUserOrgRole()
			require.NotNil(t, deleteOp)
			require.Equal(t, "testuser", deleteOp.User)
			require.Equal(t, "Viewer", deleteOp.Role)

			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testRoleChange}

		finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
		require.NoError(t, err)
		require.NotNil(t, finishFunc)

		finishFunc(context.Background(), true)
		wg.Wait()
	})

	t.Run("should update role when new role is empty", func(t *testing.T) {
		wg.Add(1)
		oldUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser2",
				Namespace: "org-2",
			},
			Spec: iamv0.UserSpec{
				Role: "Editor",
			},
		}

		newUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser2",
				Namespace: "org-2",
			},
			Spec: iamv0.UserSpec{
				Role: "",
			},
		}

		testRemoveRole := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-2", req.Namespace)
			require.Len(t, req.Operations, 2)

			// First operation should be UpdateUserOrgRole with empty role
			updateOp := req.Operations[0].GetUpdateUserOrgRole()
			require.NotNil(t, updateOp)
			require.Equal(t, "testuser2", updateOp.User)
			require.Equal(t, "", updateOp.Role)

			// Second operation should be DeleteUserOrgRole with old role
			deleteOp := req.Operations[1].GetDeleteUserOrgRole()
			require.NotNil(t, deleteOp)
			require.Equal(t, "testuser2", deleteOp.User)
			require.Equal(t, "Editor", deleteOp.Role)

			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testRemoveRole}

		finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
		require.NoError(t, err)
		require.NotNil(t, finishFunc)

		finishFunc(context.Background(), true)
		wg.Wait()
	})

	t.Run("should be able to add a new role when old role was empty", func(t *testing.T) {
		wg.Add(1)
		oldUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser3",
				Namespace: "org-3",
			},
			Spec: iamv0.UserSpec{
				Role: "",
			},
		}

		newUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser3",
				Namespace: "org-3",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		testAddRole := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-3", req.Namespace)
			require.Len(t, req.Operations, 2)

			// First operation should be UpdateUserOrgRole with new role
			updateOp := req.Operations[0].GetUpdateUserOrgRole()
			require.NotNil(t, updateOp)
			require.Equal(t, "testuser3", updateOp.User)
			require.Equal(t, "Admin", updateOp.Role)

			// Second operation should be DeleteUserOrgRole with empty old role
			deleteOp := req.Operations[1].GetDeleteUserOrgRole()
			require.NotNil(t, deleteOp)
			require.Equal(t, "testuser3", deleteOp.User)
			require.Equal(t, "", deleteOp.Role)

			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testAddRole}

		finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
		require.NoError(t, err)
		require.NotNil(t, finishFunc)

		finishFunc(context.Background(), true)
		wg.Wait()
	})

	t.Run("should skip update when role hasn't changed", func(t *testing.T) {
		oldUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser4",
				Namespace: "org-4",
			},
			Spec: iamv0.UserSpec{
				Role: "Editor",
			},
		}

		newUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser4",
				Namespace: "org-4",
			},
			Spec: iamv0.UserSpec{
				Role: "Editor",
			},
		}

		finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
		require.NoError(t, err)
		require.Nil(t, finishFunc) // Should return nil when no update needed
	})

	t.Run("should not call zanzana when update fails", func(t *testing.T) {
		oldUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser5",
				Namespace: "org-5",
			},
			Spec: iamv0.UserSpec{
				Role: "Viewer",
			},
		}

		newUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser5",
				Namespace: "org-5",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		callCount := 0
		testNoCall := func(ctx context.Context, req *v1.MutateRequest) error {
			callCount++
			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testNoCall}

		finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
		require.NoError(t, err)
		require.NotNil(t, finishFunc)

		// Call with success=false - should not trigger zanzana write
		finishFunc(context.Background(), false)
		require.Equal(t, 0, callCount, "zanzana should not be called when update fails")
	})

	t.Run("should skip when zClient is nil", func(t *testing.T) {
		builder := &IdentityAccessManagementAPIBuilder{
			logger:   log.NewNopLogger(),
			zTickets: make(chan bool, 1),
			zClient:  nil,
		}

		oldUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Viewer",
			},
		}

		newUser := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		finishFunc, err := builder.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
		require.NoError(t, err)
		require.Nil(t, finishFunc) // Should return nil when zClient is nil
	})
}

func TestAfterUserDelete(t *testing.T) {
	var wg sync.WaitGroup

	b := &IdentityAccessManagementAPIBuilder{
		logger:   log.NewNopLogger(),
		zTickets: make(chan bool, 1),
	}

	t.Run("should delete zanzana entry for user with Admin role", func(t *testing.T) {
		wg.Add(1)
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "df2p421det1q8c",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		testDeleteAdmin := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-1", req.Namespace)
			require.Len(t, req.Operations, 1)

			op := req.Operations[0]
			require.NotNil(t, op)
			deleteOp := op.GetDeleteUserOrgRole()
			require.NotNil(t, deleteOp)
			require.Equal(t, "df2p421det1q8c", deleteOp.User)
			require.Equal(t, "Admin", deleteOp.Role)

			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteAdmin}
		b.AfterUserDelete(&user, nil)
		wg.Wait()
	})

	t.Run("should delete zanzana entry for user with Editor role", func(t *testing.T) {
		wg.Add(1)
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "editor123",
				Namespace: "org-2",
			},
			Spec: iamv0.UserSpec{
				Role: "Editor",
			},
		}

		testDeleteEditor := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-2", req.Namespace)
			require.Len(t, req.Operations, 1)

			op := req.Operations[0]
			require.NotNil(t, op)
			deleteOp := op.GetDeleteUserOrgRole()
			require.NotNil(t, deleteOp)
			require.Equal(t, "editor123", deleteOp.User)
			require.Equal(t, "Editor", deleteOp.Role)

			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteEditor}
		b.AfterUserDelete(&user, nil)
		wg.Wait()
	})

	t.Run("should delete zanzana entry for user with Viewer role", func(t *testing.T) {
		wg.Add(1)
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "viewer456",
				Namespace: "org-3",
			},
			Spec: iamv0.UserSpec{
				Role: "Viewer",
			},
		}

		testDeleteViewer := func(ctx context.Context, req *v1.MutateRequest) error {
			defer wg.Done()
			require.NotNil(t, req)
			require.Equal(t, "org-3", req.Namespace)
			require.Len(t, req.Operations, 1)

			op := req.Operations[0]
			require.NotNil(t, op)
			deleteOp := op.GetDeleteUserOrgRole()
			require.NotNil(t, deleteOp)
			require.Equal(t, "viewer456", deleteOp.User)
			require.Equal(t, "Viewer", deleteOp.Role)

			return nil
		}

		b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteViewer}
		b.AfterUserDelete(&user, nil)
		wg.Wait()
	})

	t.Run("should skip when user has no role", func(t *testing.T) {
		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "norole789",
				Namespace: "org-4",
			},
			Spec: iamv0.UserSpec{
				Role: "",
			},
		}

		// Should not call zanzana client
		b.zClient = nil
		b.AfterUserDelete(&user, nil)
		// If we get here without panic, the test passes
	})

	t.Run("should skip when zClient is nil", func(t *testing.T) {
		builder := &IdentityAccessManagementAPIBuilder{
			logger:   log.NewNopLogger(),
			zTickets: make(chan bool, 1),
			zClient:  nil,
		}

		user := iamv0.User{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "testuser",
				Namespace: "org-1",
			},
			Spec: iamv0.UserSpec{
				Role: "Admin",
			},
		}

		// Should return early without calling zanzana
		builder.AfterUserDelete(&user, nil)
		// If we get here without panic, the test passes
	})
}
