// Copyright 2017 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package casbin

import (
	"strings"
	"sync"
	"testing"

	"github.com/casbin/casbin/v3/detector"
	"github.com/casbin/casbin/v3/model"
	fileadapter "github.com/casbin/casbin/v3/persist/file-adapter"
	"github.com/casbin/casbin/v3/util"
)

func TestKeyMatchModelInMemory(t *testing.T) {
	m := model.NewModel()
	m.AddDef("r", "r", "sub, obj, act")
	m.AddDef("p", "p", "sub, obj, act")
	m.AddDef("e", "e", "some(where (p.eft == allow))")
	m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")

	a := fileadapter.NewAdapter("examples/keymatch_policy.csv")

	e, _ := NewEnforcer(m, a)

	testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
	testEnforce(t, e, "alice", "/alice_data/resource1", "POST", true)
	testEnforce(t, e, "alice", "/alice_data/resource2", "GET", true)
	testEnforce(t, e, "alice", "/alice_data/resource2", "POST", false)
	testEnforce(t, e, "alice", "/bob_data/resource1", "GET", false)
	testEnforce(t, e, "alice", "/bob_data/resource1", "POST", false)
	testEnforce(t, e, "alice", "/bob_data/resource2", "GET", false)
	testEnforce(t, e, "alice", "/bob_data/resource2", "POST", false)

	testEnforce(t, e, "bob", "/alice_data/resource1", "GET", false)
	testEnforce(t, e, "bob", "/alice_data/resource1", "POST", false)
	testEnforce(t, e, "bob", "/alice_data/resource2", "GET", true)
	testEnforce(t, e, "bob", "/alice_data/resource2", "POST", false)
	testEnforce(t, e, "bob", "/bob_data/resource1", "GET", false)
	testEnforce(t, e, "bob", "/bob_data/resource1", "POST", true)
	testEnforce(t, e, "bob", "/bob_data/resource2", "GET", false)
	testEnforce(t, e, "bob", "/bob_data/resource2", "POST", true)

	testEnforce(t, e, "cathy", "/cathy_data", "GET", true)
	testEnforce(t, e, "cathy", "/cathy_data", "POST", true)
	testEnforce(t, e, "cathy", "/cathy_data", "DELETE", false)

	e, _ = NewEnforcer(m)
	_ = a.LoadPolicy(e.GetModel())

	testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
	testEnforce(t, e, "alice", "/alice_data/resource1", "POST", true)
	testEnforce(t, e, "alice", "/alice_data/resource2", "GET", true)
	testEnforce(t, e, "alice", "/alice_data/resource2", "POST", false)
	testEnforce(t, e, "alice", "/bob_data/resource1", "GET", false)
	testEnforce(t, e, "alice", "/bob_data/resource1", "POST", false)
	testEnforce(t, e, "alice", "/bob_data/resource2", "GET", false)
	testEnforce(t, e, "alice", "/bob_data/resource2", "POST", false)

	testEnforce(t, e, "bob", "/alice_data/resource1", "GET", false)
	testEnforce(t, e, "bob", "/alice_data/resource1", "POST", false)
	testEnforce(t, e, "bob", "/alice_data/resource2", "GET", true)
	testEnforce(t, e, "bob", "/alice_data/resource2", "POST", false)
	testEnforce(t, e, "bob", "/bob_data/resource1", "GET", false)
	testEnforce(t, e, "bob", "/bob_data/resource1", "POST", true)
	testEnforce(t, e, "bob", "/bob_data/resource2", "GET", false)
	testEnforce(t, e, "bob", "/bob_data/resource2", "POST", true)

	testEnforce(t, e, "cathy", "/cathy_data", "GET", true)
	testEnforce(t, e, "cathy", "/cathy_data", "POST", true)
	testEnforce(t, e, "cathy", "/cathy_data", "DELETE", false)
}

func TestKeyMatchWithRBACInDomain(t *testing.T) {
	e, _ := NewEnforcer("examples/keymatch_with_rbac_in_domain.conf", "examples/keymatch_with_rbac_in_domain.csv")
	testDomainEnforce(t, e, "Username==test2", "engines/engine1", "*", "attach", true)
}

func TestKeyMatchModelInMemoryDeny(t *testing.T) {
	m := model.NewModel()
	m.AddDef("r", "r", "sub, obj, act")
	m.AddDef("p", "p", "sub, obj, act")
	m.AddDef("e", "e", "!some(where (p.eft == deny))")
	m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")

	a := fileadapter.NewAdapter("examples/keymatch_policy.csv")

	e, _ := NewEnforcer(m, a)

	testEnforce(t, e, "alice", "/alice_data/resource2", "POST", true)
}

func TestRBACModelInMemoryIndeterminate(t *testing.T) {
	m := model.NewModel()
	m.AddDef("r", "r", "sub, obj, act")
	m.AddDef("p", "p", "sub, obj, act")
	m.AddDef("g", "g", "_, _")
	m.AddDef("e", "e", "some(where (p.eft == allow))")
	m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")

	e, _ := NewEnforcer(m)

	_, _ = e.AddPermissionForUser("alice", "data1", "invalid")

	testEnforce(t, e, "alice", "data1", "read", false)
}

func TestRBACModelInMemory(t *testing.T) {
	m := model.NewModel()
	m.AddDef("r", "r", "sub, obj, act")
	m.AddDef("p", "p", "sub, obj, act")
	m.AddDef("g", "g", "_, _")
	m.AddDef("e", "e", "some(where (p.eft == allow))")
	m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")

	e, _ := NewEnforcer(m)

	_, _ = e.AddPermissionForUser("alice", "data1", "read")
	_, _ = e.AddPermissionForUser("bob", "data2", "write")
	_, _ = e.AddPermissionForUser("data2_admin", "data2", "read")
	_, _ = e.AddPermissionForUser("data2_admin", "data2", "write")
	_, _ = e.AddRoleForUser("alice", "data2_admin")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", true)
	testEnforce(t, e, "alice", "data2", "write", true)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestRBACModelInMemory2(t *testing.T) {
	text :=
		`
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
`
	m, _ := model.NewModelFromString(text)
	// The above is the same as:
	// m := NewModel()
	// m.LoadModelFromText(text)

	e, _ := NewEnforcer(m)

	_, _ = e.AddPermissionForUser("alice", "data1", "read")
	_, _ = e.AddPermissionForUser("bob", "data2", "write")
	_, _ = e.AddPermissionForUser("data2_admin", "data2", "read")
	_, _ = e.AddPermissionForUser("data2_admin", "data2", "write")
	_, _ = e.AddRoleForUser("alice", "data2_admin")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", true)
	testEnforce(t, e, "alice", "data2", "write", true)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestNotUsedRBACModelInMemory(t *testing.T) {
	m := model.NewModel()
	m.AddDef("r", "r", "sub, obj, act")
	m.AddDef("p", "p", "sub, obj, act")
	m.AddDef("g", "g", "_, _")
	m.AddDef("e", "e", "some(where (p.eft == allow))")
	m.AddDef("m", "m", "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act")

	e, _ := NewEnforcer(m)

	_, _ = e.AddPermissionForUser("alice", "data1", "read")
	_, _ = e.AddPermissionForUser("bob", "data2", "write")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", false)
	testEnforce(t, e, "alice", "data2", "write", false)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestMatcherUsingInOperator(t *testing.T) {
	// From file config
	e, _ := NewEnforcer("examples/rbac_model_matcher_using_in_op.conf")
	_, _ = e.AddPermissionForUser("alice", "data1", "read")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data2", "read", true)
	testEnforce(t, e, "alice", "data3", "read", true)
	testEnforce(t, e, "anyone", "data1", "read", false)
	testEnforce(t, e, "anyone", "data2", "read", true)
	testEnforce(t, e, "anyone", "data3", "read", true)
}

func TestMatcherUsingInOperatorBracket(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_model_matcher_using_in_op_bracket.conf")
	_, _ = e.AddPermissionForUser("alice", "data1", "read")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data2", "read", true)
	testEnforce(t, e, "alice", "data3", "read", true)
	testEnforce(t, e, "anyone", "data1", "read", false)
	testEnforce(t, e, "anyone", "data2", "read", true)
	testEnforce(t, e, "anyone", "data3", "read", true)
}

func TestReloadPolicy(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	_ = e.LoadPolicy()
	testGetPolicy(t, e, [][]string{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"data2_admin", "data2", "read"}, {"data2_admin", "data2", "write"}})
}

func TestSavePolicy(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	_ = e.SavePolicy()
}

func TestClearPolicy(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	e.ClearPolicy()
}

func TestEnableEnforce(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")

	e.EnableEnforce(false)
	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", true)
	testEnforce(t, e, "alice", "data2", "read", true)
	testEnforce(t, e, "alice", "data2", "write", true)
	testEnforce(t, e, "bob", "data1", "read", true)
	testEnforce(t, e, "bob", "data1", "write", true)
	testEnforce(t, e, "bob", "data2", "read", true)
	testEnforce(t, e, "bob", "data2", "write", true)

	e.EnableEnforce(true)
	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", false)
	testEnforce(t, e, "alice", "data2", "write", false)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestEnableLog(t *testing.T) {
	// This test is now a no-op since the logger has been removed
	// Keeping it for backward compatibility, but it just tests enforcement
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", false)
	testEnforce(t, e, "alice", "data2", "write", false)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestEnableAutoSave(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")

	e.EnableAutoSave(false)
	// Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer,
	// it doesn't affect the policy in the storage.
	_, _ = e.RemovePolicy("alice", "data1", "read")
	// Reload the policy from the storage to see the effect.
	_ = e.LoadPolicy()
	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", false)
	testEnforce(t, e, "alice", "data2", "write", false)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)

	e.EnableAutoSave(true)
	// Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer,
	// but also affects the policy in the storage.
	_, _ = e.RemovePolicy("alice", "data1", "read")

	// However, the file adapter doesn't implement the AutoSave feature, so enabling it has no effect at all here.

	// Reload the policy from the storage to see the effect.
	_ = e.LoadPolicy()
	testEnforce(t, e, "alice", "data1", "read", true) // Will not be false here.
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", false)
	testEnforce(t, e, "alice", "data2", "write", false)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestInitWithAdapter(t *testing.T) {
	adapter := fileadapter.NewAdapter("examples/basic_policy.csv")
	e, _ := NewEnforcer("examples/basic_model.conf", adapter)

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)
	testEnforce(t, e, "alice", "data2", "read", false)
	testEnforce(t, e, "alice", "data2", "write", false)
	testEnforce(t, e, "bob", "data1", "read", false)
	testEnforce(t, e, "bob", "data1", "write", false)
	testEnforce(t, e, "bob", "data2", "read", false)
	testEnforce(t, e, "bob", "data2", "write", true)
}

func TestRoleLinks(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_model.conf")
	e.EnableAutoBuildRoleLinks(false)
	_ = e.BuildRoleLinks()
	_, _ = e.Enforce("user501", "data9", "read")
}

func TestEnforceConcurrency(t *testing.T) {
	defer func() {
		if r := recover(); r != nil {
			t.Errorf("Enforce is not concurrent")
		}
	}()

	e, _ := NewEnforcer("examples/rbac_model.conf")
	_ = e.LoadModel()

	var wg sync.WaitGroup

	// Simulate concurrency (maybe use a timer?)
	for i := 1; i <= 10000; i++ {
		wg.Add(1)
		go func() {
			_, _ = e.Enforce("user501", "data9", "read")
			wg.Done()
		}()
	}

	wg.Wait()
}

func TestGetAndSetModel(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
	e2, _ := NewEnforcer("examples/basic_with_root_model.conf", "examples/basic_policy.csv")

	testEnforce(t, e, "root", "data1", "read", false)

	e.SetModel(e2.GetModel())

	testEnforce(t, e, "root", "data1", "read", true)
}

func TestGetAndSetAdapterInMem(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
	e2, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_inverse_policy.csv")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", false)

	a2 := e2.GetAdapter()
	e.SetAdapter(a2)
	_ = e.LoadPolicy()

	testEnforce(t, e, "alice", "data1", "read", false)
	testEnforce(t, e, "alice", "data1", "write", true)
}

func TestSetAdapterFromFile(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf")

	testEnforce(t, e, "alice", "data1", "read", false)

	a := fileadapter.NewAdapter("examples/basic_policy.csv")
	e.SetAdapter(a)
	_ = e.LoadPolicy()

	testEnforce(t, e, "alice", "data1", "read", true)
}

func TestInitEmpty(t *testing.T) {
	e, _ := NewEnforcer()

	m := model.NewModel()
	m.AddDef("r", "r", "sub, obj, act")
	m.AddDef("p", "p", "sub, obj, act")
	m.AddDef("e", "e", "some(where (p.eft == allow))")
	m.AddDef("m", "m", "r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)")

	a := fileadapter.NewAdapter("examples/keymatch_policy.csv")

	e.SetModel(m)
	e.SetAdapter(a)
	_ = e.LoadPolicy()

	testEnforce(t, e, "alice", "/alice_data/resource1", "GET", true)
}
func testEnforceEx(t *testing.T, e *Enforcer, sub, obj, act interface{}, res []string) {
	t.Helper()
	_, myRes, _ := e.EnforceEx(sub, obj, act)

	if ok := util.ArrayEquals(res, myRes); !ok {
		t.Error("Key: ", myRes, ", supposed to be ", res)
	}
}

func TestEnforceEx(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")

	testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read"})
	testEnforceEx(t, e, "alice", "data1", "write", []string{})
	testEnforceEx(t, e, "alice", "data2", "read", []string{})
	testEnforceEx(t, e, "alice", "data2", "write", []string{})
	testEnforceEx(t, e, "bob", "data1", "read", []string{})
	testEnforceEx(t, e, "bob", "data1", "write", []string{})
	testEnforceEx(t, e, "bob", "data2", "read", []string{})
	testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write"})

	e, _ = NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read"})
	testEnforceEx(t, e, "alice", "data1", "write", []string{})
	testEnforceEx(t, e, "alice", "data2", "read", []string{"data2_admin", "data2", "read"})
	testEnforceEx(t, e, "alice", "data2", "write", []string{"data2_admin", "data2", "write"})
	testEnforceEx(t, e, "bob", "data1", "read", []string{})
	testEnforceEx(t, e, "bob", "data1", "write", []string{})
	testEnforceEx(t, e, "bob", "data2", "read", []string{})
	testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write"})

	e, _ = NewEnforcer("examples/priority_model.conf", "examples/priority_policy.csv")

	testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read", "allow"})
	testEnforceEx(t, e, "alice", "data1", "write", []string{"data1_deny_group", "data1", "write", "deny"})
	testEnforceEx(t, e, "alice", "data2", "read", []string{})
	testEnforceEx(t, e, "alice", "data2", "write", []string{})
	testEnforceEx(t, e, "bob", "data1", "write", []string{})
	testEnforceEx(t, e, "bob", "data2", "read", []string{"data2_allow_group", "data2", "read", "allow"})
	testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write", "deny"})

	e, _ = NewEnforcer("examples/abac_model.conf")
	obj := struct{ Owner string }{Owner: "alice"}
	testEnforceEx(t, e, "alice", obj, "write", []string{})
}

func TestEnforceExLog(t *testing.T) {
	// This test was previously named for logging, but actually tests EnforceEx explain functionality
	// Logger parameter has been removed, but the test still validates explain behavior
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")

	testEnforceEx(t, e, "alice", "data1", "read", []string{"alice", "data1", "read"})
	testEnforceEx(t, e, "alice", "data1", "write", []string{})
	testEnforceEx(t, e, "alice", "data2", "read", []string{})
	testEnforceEx(t, e, "alice", "data2", "write", []string{})
	testEnforceEx(t, e, "bob", "data1", "read", []string{})
	testEnforceEx(t, e, "bob", "data1", "write", []string{})
	testEnforceEx(t, e, "bob", "data2", "read", []string{})
	testEnforceEx(t, e, "bob", "data2", "write", []string{"bob", "data2", "write"})
}

func testBatchEnforce(t *testing.T, e *Enforcer, requests [][]interface{}, results []bool) {
	t.Helper()
	myRes, _ := e.BatchEnforce(requests)
	if len(myRes) != len(results) {
		t.Errorf("%v supposed to be %v", myRes, results)
	}
	for i, v := range myRes {
		if v != results[i] {
			t.Errorf("%v supposed to be %v", myRes, results)
		}
	}
}

func TestBatchEnforce(t *testing.T) {
	e, _ := NewEnforcer("examples/basic_model.conf", "examples/basic_policy.csv")
	results := []bool{true, true, false}
	testBatchEnforce(t, e, [][]interface{}{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"jack", "data3", "read"}}, results)
}

func TestSubjectPriority(t *testing.T) {
	e, _ := NewEnforcer("examples/subject_priority_model.conf", "examples/subject_priority_policy.csv")
	testBatchEnforce(t, e, [][]interface{}{
		{"jane", "data1", "read"},
		{"alice", "data1", "read"},
	}, []bool{
		true, true,
	})
}

func TestSubjectPriorityWithDomain(t *testing.T) {
	e, _ := NewEnforcer("examples/subject_priority_model_with_domain.conf", "examples/subject_priority_policy_with_domain.csv")
	testBatchEnforce(t, e, [][]interface{}{
		{"alice", "data1", "domain1", "write"},
		{"bob", "data2", "domain2", "write"},
	}, []bool{
		true, true,
	})
}

func TestSubjectPriorityInFilter(t *testing.T) {
	e, _ := NewEnforcer()

	adapter := fileadapter.NewFilteredAdapter("examples/subject_priority_policy_with_domain.csv")
	_ = e.InitWithAdapter("examples/subject_priority_model_with_domain.conf", adapter)
	if err := e.loadFilteredPolicy(&fileadapter.Filter{
		P: []string{"", "", "domain1"},
	}); err != nil {
		t.Errorf("unexpected error in LoadFilteredPolicy: %v", err)
	}

	testBatchEnforce(t, e, [][]interface{}{
		{"alice", "data1", "domain1", "write"},
		{"admin", "data1", "domain1", "write"},
	}, []bool{
		true, false,
	})
}

func TestMultiplePolicyDefinitions(t *testing.T) {
	e, _ := NewEnforcer("examples/multiple_policy_definitions_model.conf", "examples/multiple_policy_definitions_policy.csv")
	enforceContext := NewEnforceContext("2")
	enforceContext.EType = "e"
	testBatchEnforce(t, e, [][]interface{}{
		{"alice", "data2", "read"},
		{enforceContext, struct{ Age int }{Age: 70}, "/data1", "read"},
		{enforceContext, struct{ Age int }{Age: 30}, "/data1", "read"},
	}, []bool{
		true, false, true,
	})
}

func TestPriorityExplicit(t *testing.T) {
	e, _ := NewEnforcer("examples/priority_model_explicit.conf", "examples/priority_policy_explicit.csv")
	testBatchEnforce(t, e, [][]interface{}{
		{"alice", "data1", "write"},
		{"alice", "data1", "read"},
		{"bob", "data2", "read"},
		{"bob", "data2", "write"},
		{"data1_deny_group", "data1", "read"},
		{"data1_deny_group", "data1", "write"},
		{"data2_allow_group", "data2", "read"},
		{"data2_allow_group", "data2", "write"},
	}, []bool{
		true, true, false, true, false, false, true, true,
	})

	_, err := e.AddPolicy("1", "bob", "data2", "write", "deny")
	if err != nil {
		t.Fatalf("Add Policy: %v", err)
	}

	testBatchEnforce(t, e, [][]interface{}{
		{"alice", "data1", "write"},
		{"alice", "data1", "read"},
		{"bob", "data2", "read"},
		{"bob", "data2", "write"},
		{"data1_deny_group", "data1", "read"},
		{"data1_deny_group", "data1", "write"},
		{"data2_allow_group", "data2", "read"},
		{"data2_allow_group", "data2", "write"},
	}, []bool{
		true, true, false, false, false, false, true, true,
	})
}

func TestFailedToLoadPolicy(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
	e.AddNamedMatchingFunc("g2", "matchingFunc", util.KeyMatch2)
	testEnforce(t, e, "alice", "/book/1", "GET", true)
	testEnforce(t, e, "bob", "/pen/3", "GET", true)
	e.SetAdapter(fileadapter.NewAdapter("not found"))
	_ = e.LoadPolicy()
	testEnforce(t, e, "alice", "/book/1", "GET", true)
	testEnforce(t, e, "bob", "/pen/3", "GET", true)
}

func TestReloadPolicyWithFunc(t *testing.T) {
	e, _ := NewEnforcer("examples/rbac_with_pattern_model.conf", "examples/rbac_with_pattern_policy.csv")
	e.AddNamedMatchingFunc("g2", "matchingFunc", util.KeyMatch2)
	testEnforce(t, e, "alice", "/book/1", "GET", true)
	testEnforce(t, e, "bob", "/pen/3", "GET", true)
	_ = e.LoadPolicy()
	testEnforce(t, e, "alice", "/book/1", "GET", true)
	testEnforce(t, e, "bob", "/pen/3", "GET", true)
}

func TestEvalPriority(t *testing.T) {
	e, _ := NewEnforcer("examples/eval_operator_model.conf", "examples/eval_operator_policy.csv")
	testEnforce(t, e, "admin", "users", "write", true)
	testEnforce(t, e, "admin", "none", "write", false)
	testEnforce(t, e, "user", "users", "write", false)
}

func TestLinkConditionFunc(t *testing.T) {
	TrueFunc := func(args ...string) (bool, error) {
		if len(args) != 0 {
			return args[0] == "_" || args[0] == "true", nil
		}
		return false, nil
	}

	FalseFunc := func(args ...string) (bool, error) {
		if len(args) != 0 {
			return args[0] == "_" || args[0] == "false", nil
		}
		return false, nil
	}

	m, _ := model.NewModelFromFile("examples/rbac_with_temporal_roles_model.conf")
	e, _ := NewEnforcer(m)

	_, _ = e.AddPolicies([][]string{
		{"alice", "data1", "read"},
		{"alice", "data1", "write"},
		{"data2_admin", "data2", "read"},
		{"data2_admin", "data2", "write"},
		{"data3_admin", "data3", "read"},
		{"data3_admin", "data3", "write"},
		{"data4_admin", "data4", "read"},
		{"data4_admin", "data4", "write"},
		{"data5_admin", "data5", "read"},
		{"data5_admin", "data5", "write"},
	})

	_, _ = e.AddGroupingPolicies([][]string{
		{"alice", "data2_admin", "_", "_"},
		{"alice", "data3_admin", "_", "_"},
		{"alice", "data4_admin", "_", "_"},
		{"alice", "data5_admin", "_", "_"},
	})

	e.AddNamedLinkConditionFunc("g", "alice", "data2_admin", TrueFunc)
	e.AddNamedLinkConditionFunc("g", "alice", "data3_admin", TrueFunc)
	e.AddNamedLinkConditionFunc("g", "alice", "data4_admin", FalseFunc)
	e.AddNamedLinkConditionFunc("g", "alice", "data5_admin", FalseFunc)

	e.SetNamedLinkConditionFuncParams("g", "alice", "data2_admin", "true")
	e.SetNamedLinkConditionFuncParams("g", "alice", "data3_admin", "not true")
	e.SetNamedLinkConditionFuncParams("g", "alice", "data4_admin", "false")
	e.SetNamedLinkConditionFuncParams("g", "alice", "data5_admin", "not false")

	testEnforce(t, e, "alice", "data1", "read", true)
	testEnforce(t, e, "alice", "data1", "write", true)
	testEnforce(t, e, "alice", "data2", "read", true)
	testEnforce(t, e, "alice", "data2", "write", true)
	testEnforce(t, e, "alice", "data3", "read", false)
	testEnforce(t, e, "alice", "data3", "write", false)
	testEnforce(t, e, "alice", "data4", "read", true)
	testEnforce(t, e, "alice", "data4", "write", true)
	testEnforce(t, e, "alice", "data5", "read", false)
	testEnforce(t, e, "alice", "data5", "write", false)

	m, _ = model.NewModelFromFile("examples/rbac_with_domain_temporal_roles_model.conf")
	e, _ = NewEnforcer(m)

	_, _ = e.AddPolicies([][]string{
		{"alice", "domain1", "data1", "read"},
		{"alice", "domain1", "data1", "write"},
		{"data2_admin", "domain2", "data2", "read"},
		{"data2_admin", "domain2", "data2", "write"},
		{"data3_admin", "domain3", "data3", "read"},
		{"data3_admin", "domain3", "data3", "write"},
		{"data4_admin", "domain4", "data4", "read"},
		{"data4_admin", "domain4", "data4", "write"},
		{"data5_admin", "domain5", "data5", "read"},
		{"data5_admin", "domain5", "data5", "write"},
	})

	_, _ = e.AddGroupingPolicies([][]string{
		{"alice", "data2_admin", "domain2", "_", "_"},
		{"alice", "data3_admin", "domain3", "_", "_"},
		{"alice", "data4_admin", "domain4", "_", "_"},
		{"alice", "data5_admin", "domain5", "_", "_"},
	})

	e.AddNamedDomainLinkConditionFunc("g", "alice", "data2_admin", "domain2", TrueFunc)
	e.AddNamedDomainLinkConditionFunc("g", "alice", "data3_admin", "domain3", TrueFunc)
	e.AddNamedDomainLinkConditionFunc("g", "alice", "data4_admin", "domain4", FalseFunc)
	e.AddNamedDomainLinkConditionFunc("g", "alice", "data5_admin", "domain5", FalseFunc)

	e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data2_admin", "domain2", "true")
	e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data3_admin", "domain3", "not true")
	e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data4_admin", "domain4", "false")
	e.SetNamedDomainLinkConditionFuncParams("g", "alice", "data5_admin", "domain5", "not false")

	testDomainEnforce(t, e, "alice", "domain1", "data1", "read", true)
	testDomainEnforce(t, e, "alice", "domain1", "data1", "write", true)
	testDomainEnforce(t, e, "alice", "domain2", "data2", "read", true)
	testDomainEnforce(t, e, "alice", "domain2", "data2", "write", true)
	testDomainEnforce(t, e, "alice", "domain3", "data3", "read", false)
	testDomainEnforce(t, e, "alice", "domain3", "data3", "write", false)
	testDomainEnforce(t, e, "alice", "domain4", "data4", "read", true)
	testDomainEnforce(t, e, "alice", "domain4", "data4", "write", true)
	testDomainEnforce(t, e, "alice", "domain5", "data5", "read", false)
	testDomainEnforce(t, e, "alice", "domain5", "data5", "write", false)
}

func TestEnforcerWithDefaultDetector(t *testing.T) {
	// Test that default detector is enabled and detects cycles
	_, err := NewEnforcer("examples/rbac_model.conf", "examples/rbac_with_cycle_policy.csv")

	// Expect an error because the policy contains a cycle
	if err == nil {
		t.Error("Expected cycle detection error when loading policy with cycle, but got nil")
	} else {
		errMsg := err.Error()
		if !strings.Contains(errMsg, "cycle detected") {
			t.Errorf("Expected error message to contain 'cycle detected', got: %s", errMsg)
		}
	}
}

func TestEnforcerRunDetections(t *testing.T) {
	// Test explicit RunDetections() call
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	// Should not error on valid policy
	err := e.RunDetections()
	if err != nil {
		t.Errorf("Expected no error when running detections on valid policy, but got: %v", err)
	}

	// Now add a cycle manually
	_, _ = e.AddGroupingPolicy("alice", "data2_admin")
	_, _ = e.AddGroupingPolicy("data2_admin", "super_admin")
	_, _ = e.AddGroupingPolicy("super_admin", "alice")

	// Should detect the cycle
	err = e.RunDetections()
	if err == nil {
		t.Error("Expected cycle detection error, but got nil")
	} else {
		errMsg := err.Error()
		if !strings.Contains(errMsg, "cycle detected") {
			t.Errorf("Expected error message to contain 'cycle detected', got: %s", errMsg)
		}
	}
}

func TestEnforcerSetDetector(t *testing.T) {
	// Test SetDetector() method
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	// Create a custom detector
	customDetector := detector.NewDefaultDetector()
	e.SetDetector(customDetector)

	// Should still work with custom detector
	err := e.RunDetections()
	if err != nil {
		t.Errorf("Expected no error with custom detector, but got: %v", err)
	}
}

func TestEnforcerSetDetectors(t *testing.T) {
	// Test SetDetectors() method
	e, _ := NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")

	// Create multiple detectors
	detectors := []detector.Detector{
		detector.NewDefaultDetector(),
		detector.NewDefaultDetector(),
	}
	e.SetDetectors(detectors)

	// Should work with multiple detectors
	err := e.RunDetections()
	if err != nil {
		t.Errorf("Expected no error with multiple detectors, but got: %v", err)
	}
}
