package auth

import (
	"testing"
)

func TestAdminBypass(t *testing.T) {
	u := &User{Role: RoleAdmin}
	for _, perm := range []string{"foo.bar", "nonexistent.thing", "system.*"} {
		if !u.Can(perm) {
			t.Errorf("admin should bypass any permission, failed: %s", perm)
		}
	}
}

func TestRoleGlobMatching(t *testing.T) {
	installRoleFixture(t)
	cases := []struct {
		role Role
		perm string
		want bool
	}{
		{RoleSales, "sales.orders.index", true},
		{RoleSales, "sales.orders.store", true},
		{RoleSales, "masterdata.customers.read", true},
		{RoleSales, "masterdata.units.*.list", false}, // no recursion
		{RoleSales, "purchasing.purchase_orders.index", false},
		{RoleFinance, "purchasing.purchase-orders.view", true},   // explicit grant
		{RoleFinance, "purchasing.purchase-orders.approve", false}, // finance cannot approve
		{RoleFinance, "accounting.journals.post", true},           // accounting.* glob
		{RoleFinanceHead, "purchasing.purchase-orders.approve", true},
		{RoleFinanceHead, "purchasing.purchase-orders.view", true},
		{RoleWarehouse, "inventory.stocks.index", true},
		// RBAC audit 2026-09-11: only Kepala Finance approves POs; warehouse
		// creates/edits/submits/receives but never self-approves.
		{RoleWarehouse, "purchasing.purchase-orders.approve", false},
		{RoleWarehouse, "purchasing.purchase-orders.create", true},
		{RoleWarehouse, "purchasing.goods-receipts.create", true},
		{RoleGM, "purchasing.purchase-orders.approve", false},
		{RoleGM, "purchasing.purchase-orders.create", true},
		{RoleBM, "purchasing.purchase-orders.approve", false},
		{RoleBM, "purchasing.purchase-orders.create", true},
		{RoleWarehouse, "sales.orders.index", false},
		{RoleHRManager, "hr.payrolls.generate", true},
		{RoleHRStaff, "hr.leaves.approve", true},
		{RoleHRStaff, "hr.payrolls.generate", false},
		{RoleEmployee, "hr.clockin", true},
		{RoleEmployee, "hr.leaves.store", true},
		{RoleEmployee, "hr.leaves.index", false},
		{RoleSystem, "system.roles.*", true},
		{RoleSystem, "inventory.stocks.index", false},
		// admin listed for completeness
		{RoleAdmin, "anything.at.all", true},
	}
	for _, c := range cases {
		u := &User{Role: c.role}
		if got := u.Can(c.perm); got != c.want {
			t.Errorf("Can(%s, %s) = %v, want %v", c.role, c.perm, got, c.want)
		}
	}
}

func TestPerUserOverrideGrant(t *testing.T) {
	installRoleFixture(t)
	// sales gains purchasing via explicit JSON override
	u := &User{Role: RoleSales, Permissions: Permissions{"purchasing.purchase_orders.index": true}}
	if !u.Can("purchasing.purchase_orders.index") {
		t.Error("override should grant purchasing permission")
	}
	if !u.Can("sales.orders.index") { // role globs still apply
		t.Error("role globs should still apply alongside override")
	}
}

func TestPerUserOverrideRevoke(t *testing.T) {
	installRoleFixture(t)
	// sales loses sales.* via explicit false (Laravel JSON override semantics)
	u := &User{Role: RoleSales, Permissions: Permissions{"sales.orders.index": false}}
	if u.Can("sales.orders.index") {
		t.Error("explicit false override should revoke permission")
	}
	if !u.Can("sales.customers.index") {
		t.Error("other sales.* globs must remain granted")
	}
}

func TestEffectivePermissionsIncludesOverride(t *testing.T) {
	installRoleFixture(t)
	u := &User{Role: RoleSales, Permissions: Permissions{"hr.clockout": true}}
	eff := u.EffectivePermissions()
	if !eff["masterdata.customers.*"] {
		t.Error("role glob should be present in effective set")
	}
	if !eff["sales.*"] {
		t.Error("sales glob should be present")
	}
	if !eff["hr.clockout"] {
		t.Error("override permission should be present")
	}
}

func TestEffectivePermissionsAdminWildcard(t *testing.T) {
	// admin/owner bypass server-side (Can); the published set must carry a
	// wildcard so frontends gate the same way instead of seeing an empty set.
	for _, r := range []Role{RoleAdmin, RoleOwner} {
		eff := (&User{Role: r}).EffectivePermissions()
		if !eff["*"] {
			t.Errorf("role %q should publish wildcard '*'", r)
		}
	}
	eff := (&User{Role: RoleSales}).EffectivePermissions()
	if eff["*"] {
		t.Error("non-bypass roles must not publish wildcard '*'")
	}
}

func TestGlobMatchUtil(t *testing.T) {
	cases := []struct {
		pattern, s string
		want       bool
	}{
		{"sales.*", "sales.orders.index", true},
		{"sales.*", "sales", false},
		{"sales.*", "salesx.orders.index", false},
		{"masterdata.*", "masterdata.customers.read", true},
		{"hr.clockin", "hr.clockin", true},
		{"hr.clockin", "hr.clockin2", false},
		{"a.b.c", "a.b.x", false},
	}
	for _, c := range cases {
		if got := globMatch(c.pattern, c.s); got != c.want {
			t.Errorf("globMatch(%q, %q) = %v, want %v", c.pattern, c.s, got, c.want)
		}
	}
}

func TestPerUserGlobFalseDeniesRoleGrant(t *testing.T) {
	installRoleFixture(t)
	// Wildcard deny in users.permissions must beat the role glob.
	u := &User{Role: RoleSales, Permissions: Permissions{"sales.*": false}}
	if u.Can("sales.orders.index") {
		t.Error("glob false override should deny sales.orders.index")
	}
	if !u.Can("masterdata.customers.read") {
		t.Error("unrelated role globs must remain granted")
	}
}

func TestPerUserGlobTrueGrantsNothing(t *testing.T) {
	installRoleFixture(t)
	// A glob-shaped true must not widen the role; grants stay exact-only.
	u := &User{Role: RoleCashier, Permissions: Permissions{"accounting.*": true}}
	if u.Can("accounting.journals.post") {
		t.Error("glob true override must not grant accounting.journals.post")
	}
}

func TestEffectivePermissionsFalseRemoves(t *testing.T) {
	installRoleFixture(t)
	u := &User{Role: RoleSales, Permissions: Permissions{"sales.orders.index": false}}
	eff := u.EffectivePermissions()
	if _, ok := eff["sales.orders.index"]; ok {
		t.Error("explicit false must remove the key, not publish false")
	}
	if !eff["sales.*"] {
		t.Error("role globs must remain published")
	}
}
