package coderdtest_test

import (
	"context"
	"math/rand"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/coder/coder/v2/coderd/coderdtest"
	"github.com/coder/coder/v2/coderd/rbac"
)

func TestAuthzRecorder(t *testing.T) {
	t.Parallel()

	t.Run("Authorize", func(t *testing.T) {
		t.Parallel()

		rec := &coderdtest.RecordingAuthorizer{
			Wrapped: &coderdtest.FakeAuthorizer{},
		}
		sub := coderdtest.RandomRBACSubject()
		pairs := fuzzAuthz(t, sub, rec, 10)
		rec.AssertActor(t, sub, pairs...)
		require.NoError(t, rec.AllAsserted(), "all assertions should have been made")
	})

	t.Run("Authorize2Subjects", func(t *testing.T) {
		t.Parallel()

		rec := &coderdtest.RecordingAuthorizer{
			Wrapped: &coderdtest.FakeAuthorizer{},
		}
		a := coderdtest.RandomRBACSubject()
		aPairs := fuzzAuthz(t, a, rec, 10)

		b := coderdtest.RandomRBACSubject()
		bPairs := fuzzAuthz(t, b, rec, 10)

		rec.AssertActor(t, b, bPairs...)
		rec.AssertActor(t, a, aPairs...)
		require.NoError(t, rec.AllAsserted(), "all assertions should have been made")
	})

	t.Run("Authorize&Prepared", func(t *testing.T) {
		t.Parallel()

		rec := &coderdtest.RecordingAuthorizer{
			Wrapped: &coderdtest.FakeAuthorizer{},
		}
		a := coderdtest.RandomRBACSubject()
		aPairs := fuzzAuthz(t, a, rec, 10)

		b := coderdtest.RandomRBACSubject()

		act, objTy := coderdtest.RandomRBACAction(), coderdtest.RandomRBACObject().Type
		prep, _ := rec.Prepare(context.Background(), b, act, objTy)
		bPairs := fuzzAuthzPrep(t, prep, 10, act, objTy)

		rec.AssertActor(t, b, bPairs...)
		rec.AssertActor(t, a, aPairs...)
		require.NoError(t, rec.AllAsserted(), "all assertions should have been made")
	})

	t.Run("AuthorizeOutOfOrder", func(t *testing.T) {
		t.Parallel()

		rec := &coderdtest.RecordingAuthorizer{
			Wrapped: &coderdtest.FakeAuthorizer{},
		}
		sub := coderdtest.RandomRBACSubject()
		pairs := fuzzAuthz(t, sub, rec, 10)
		rand.Shuffle(len(pairs), func(i, j int) {
			pairs[i], pairs[j] = pairs[j], pairs[i]
		})

		rec.AssertOutOfOrder(t, sub, pairs...)
		require.NoError(t, rec.AllAsserted(), "all assertions should have been made")
	})

	t.Run("AllCalls", func(t *testing.T) {
		t.Parallel()

		rec := &coderdtest.RecordingAuthorizer{
			Wrapped: &coderdtest.FakeAuthorizer{},
		}
		sub := coderdtest.RandomRBACSubject()
		calls := rec.AllCalls(&sub)
		pairs := make([]coderdtest.ActionObjectPair, 0, len(calls))
		for _, call := range calls {
			pairs = append(pairs, coderdtest.ActionObjectPair{
				Action: call.Action,
				Object: call.Object,
			})
		}

		rec.AssertActor(t, sub, pairs...)
		require.NoError(t, rec.AllAsserted(), "all assertions should have been made")
	})
}

// fuzzAuthzPrep has same action and object types for all calls.
func fuzzAuthzPrep(t *testing.T, prep rbac.PreparedAuthorized, n int, action rbac.Action, objectType string) []coderdtest.ActionObjectPair {
	t.Helper()
	pairs := make([]coderdtest.ActionObjectPair, 0, n)

	for i := 0; i < n; i++ {
		obj := coderdtest.RandomRBACObject()
		obj.Type = objectType
		p := coderdtest.ActionObjectPair{Action: action, Object: obj}
		_ = prep.Authorize(context.Background(), p.Object)
		pairs = append(pairs, p)
	}
	return pairs
}

func fuzzAuthz(t *testing.T, sub rbac.Subject, rec rbac.Authorizer, n int) []coderdtest.ActionObjectPair {
	t.Helper()
	pairs := make([]coderdtest.ActionObjectPair, 0, n)

	for i := 0; i < n; i++ {
		p := coderdtest.ActionObjectPair{Action: coderdtest.RandomRBACAction(), Object: coderdtest.RandomRBACObject()}
		_ = rec.Authorize(context.Background(), sub, p.Action, p.Object)
		pairs = append(pairs, p)
	}
	return pairs
}
