package zddc

import "testing"

// helpers

func chain(levels ...ZddcFile) PolicyChain {
	return PolicyChain{Levels: levels, HasAnyFile: len(levels) > 0}
}

func perms(p map[string]string) ZddcFile {
	return ZddcFile{ACL: ACLRules{Permissions: p}}
}

// TestDelegated_LeafGrantOverridesAncestorDeny verifies the historical
// commercial behavior preserved as ModeDelegated.
func TestDelegated_LeafGrantOverridesAncestorDeny(t *testing.T) {
	c := chain(
		perms(map[string]string{"vendor_acme": ""}),  // root: deny
		ZddcFile{ // mid: define the role
			ACL:   ACLRules{},
			Roles: map[string]Role{"vendor_acme": {Members: []string{"*@acme.com"}}},
		},
		perms(map[string]string{"vendor_acme": "rwcd"}), // leaf: allow
	)
	// Need the role definition to flow up to root for the deny entry to
	// match acme members. Add the role at root too.
	c.Levels[0].Roles = map[string]Role{"vendor_acme": {Members: []string{"*@acme.com"}}}

	if !AllowedAction(c, "rep@acme.com", VerbR, ModeDelegated) {
		t.Errorf("delegated mode: leaf rwcd should override root deny for read")
	}
	if !AllowedAction(c, "rep@acme.com", VerbW, ModeDelegated) {
		t.Errorf("delegated mode: leaf rwcd should override root deny for write")
	}
}

func TestStrict_AncestorDenyAbsolute(t *testing.T) {
	c := chain(
		ZddcFile{
			ACL:   ACLRules{Permissions: map[string]string{"vendor_acme": ""}},
			Roles: map[string]Role{"vendor_acme": {Members: []string{"*@acme.com"}}},
		},
		ZddcFile{
			ACL: ACLRules{Permissions: map[string]string{"vendor_acme": "rwcd"}},
		},
	)
	if AllowedAction(c, "rep@acme.com", VerbR, ModeStrict) {
		t.Errorf("strict mode: root deny should not be overridable by leaf grant")
	}
	if AllowedAction(c, "rep@acme.com", VerbW, ModeStrict) {
		t.Errorf("strict mode: root deny should not be overridable by leaf grant (write)")
	}
}

func TestStrict_NoAncestorDenyMeansLeafDecides(t *testing.T) {
	c := chain(
		ZddcFile{
			ACL: ACLRules{Permissions: map[string]string{"_company": "r"}},
			Roles: map[string]Role{"_company": {Members: []string{"*@mycompany.com"}}},
		},
		perms(map[string]string{"alice@mycompany.com": "rwcd"}),
	)
	if !AllowedAction(c, "alice@mycompany.com", VerbW, ModeStrict) {
		t.Errorf("strict: leaf grant should decide when no ancestor explicit-deny matches")
	}
}

func TestStrict_AncestorDenyOnRoleSpecificEntryDoesNotBlockOthers(t *testing.T) {
	// Root denies vendor_acme but grants _company. acme is locked out
	// under strict; mycompany staff still see leaf grants.
	c := chain(
		ZddcFile{
			ACL: ACLRules{Permissions: map[string]string{
				"vendor_acme": "",
				"_company":    "r",
			}},
			Roles: map[string]Role{
				"vendor_acme": {Members: []string{"*@acme.com"}},
				"_company":    {Members: []string{"*@mycompany.com"}},
			},
		},
		perms(map[string]string{"_company": "rwcd"}),
	)
	if AllowedAction(c, "rep@acme.com", VerbR, ModeStrict) {
		t.Errorf("strict: acme should be denied (root deny is absolute)")
	}
	if !AllowedAction(c, "alice@mycompany.com", VerbW, ModeStrict) {
		t.Errorf("strict: mycompany's leaf grant should still apply (no matching ancestor deny)")
	}
}

func TestParseCascadeMode(t *testing.T) {
	cases := map[string]CascadeMode{
		"":          ModeDelegated,
		"delegated": ModeDelegated,
		"strict":    ModeStrict,
	}
	for in, want := range cases {
		got, ok := ParseCascadeMode(in)
		if !ok || got != want {
			t.Errorf("ParseCascadeMode(%q) = %v %v, want %v true", in, got, ok, want)
		}
	}
	if _, ok := ParseCascadeMode("loose"); ok {
		t.Errorf("ParseCascadeMode(\"loose\") should be ok=false")
	}
}
