package policy

import (
	"context"
	"testing"

	"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"

	"github.com/open-policy-agent/opa/rego"
)

// TestFederalRego_DivergencesFromStandard validates the federal-mode
// variant by asserting both that:
//
//   (a) most cascade scenarios produce the same verdict as standard
//       (the federal rule reduces to standard whenever no parent deny
//       intersects a leaf allow), AND
//
//   (b) the specific scenarios where the rules differ (a leaf-level
//       allow overlaying an ancestor's deny) produce DIFFERENT verdicts:
//       standard says allow (leaf wins); federal says deny (ancestor
//       deny is absolute — NIST AC-6 default).
//
// Like the standard parity test, this imports the OPA library as a
// test-only dependency. The federal Rego is a deployable artifact
// (operators dump it via --print-rego=federal); the parity guard
// here proves the artifact behaves as documented.
func TestFederalRego_DivergencesFromStandard(t *testing.T) {
	ctx := context.Background()

	standard, err := rego.New(
		rego.Query("data.zddc.access.allow"),
		rego.Module("access.rego", ReferenceRego),
	).PrepareForEval(ctx)
	if err != nil {
		t.Fatalf("compile standard rego: %v", err)
	}
	federal, err := rego.New(
		rego.Query("data.zddc.access_federal.allow"),
		rego.Module("access_federal.rego", FederalRego),
	).PrepareForEval(ctx)
	if err != nil {
		t.Fatalf("compile federal rego: %v", err)
	}

	allow := func(p ...string) zddc.ZddcFile { return zddc.ZddcFile{ACL: zddc.ACLRules{Allow: p}} }
	deny := func(p ...string) zddc.ZddcFile { return zddc.ZddcFile{ACL: zddc.ACLRules{Deny: p}} }
	empty := zddc.ZddcFile{}

	cases := []struct {
		name             string
		chain            zddc.PolicyChain
		email            string
		wantStandard     bool
		wantFederal      bool
		divergesByDesign bool // true if standard and federal must disagree here
	}{
		// ── Cases where the two policies must AGREE ────────────────
		{
			"empty chain, no files",
			zddc.PolicyChain{HasAnyFile: false},
			"alice@example.com",
			true, true, false,
		},
		{
			"files exist, no rule matches → both deny",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{allow("*@trusted.com")}, HasAnyFile: true},
			"alice@example.com",
			false, false, false,
		},
		{
			"leaf allow with no ancestor deny → both allow",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{empty, allow("*@example.com")}, HasAnyFile: true},
			"alice@example.com",
			true, true, false,
		},
		{
			"only deny anywhere → both deny",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{deny("alice@example.com")}, HasAnyFile: true},
			"alice@example.com",
			false, false, false,
		},
		{
			"glob allow, no deny → both allow",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{allow("*@example.com")}, HasAnyFile: true},
			"alice@example.com",
			true, true, false,
		},

		// ── The signature divergence: leaf allow overlaying ancestor deny ──
		{
			"leaf allows what parent denied → standard allows, federal denies (AC-6)",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{
				deny("alice@example.com"),
				allow("alice@example.com"),
			}, HasAnyFile: true},
			"alice@example.com",
			true,  // standard: leaf wins
			false, // federal: parent deny is absolute
			true,
		},
		{
			"deep leaf re-allows after middle deny → standard allows, federal denies",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{
				allow("*@example.com"),
				deny("alice@example.com"),
				allow("alice@example.com"),
			}, HasAnyFile: true},
			"alice@example.com",
			true,
			false,
			true,
		},
		{
			"glob deny at root, specific allow at leaf → both differ",
			zddc.PolicyChain{Levels: []zddc.ZddcFile{
				deny("*@example.com"),
				allow("alice@example.com"),
			}, HasAnyFile: true},
			"alice@example.com",
			true,
			false,
			true,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			input := AllowInput{Path: "/test", PolicyChain: chainToSerializable(tc.chain)}
			input.User.Email = tc.email
			regoInput, err := canonicalInput(input)
			if err != nil {
				t.Fatalf("encode input: %v", err)
			}

			std, err := standard.Eval(ctx, rego.EvalInput(regoInput))
			if err != nil {
				t.Fatalf("standard eval: %v", err)
			}
			fed, err := federal.Eval(ctx, rego.EvalInput(regoInput))
			if err != nil {
				t.Fatalf("federal eval: %v", err)
			}
			if len(std) == 0 || len(fed) == 0 {
				t.Fatal("rego returned empty result set")
			}
			stdAllow := std[0].Expressions[0].Value.(bool)
			fedAllow := fed[0].Expressions[0].Value.(bool)

			if stdAllow != tc.wantStandard {
				t.Errorf("standard rego: got %v, want %v", stdAllow, tc.wantStandard)
			}
			if fedAllow != tc.wantFederal {
				t.Errorf("federal rego: got %v, want %v", fedAllow, tc.wantFederal)
			}
			// Cross-check the divergence flag itself: if we said the cases
			// must disagree, they must; if we said they agree, they must.
			diverges := stdAllow != fedAllow
			if diverges != tc.divergesByDesign {
				t.Errorf("divergence = %v, want %v (standard=%v, federal=%v)",
					diverges, tc.divergesByDesign, stdAllow, fedAllow)
			}
		})
	}
}

// TestFederalRego_RegoCompiles is a sanity check that the embedded
// federal Rego file parses without error in OPA, separate from the
// behavior tests. Catches accidental syntax breakage in
// access_federal.rego before running the (slower) parity matrix.
func TestFederalRego_RegoCompiles(t *testing.T) {
	_, err := rego.New(
		rego.Query("data.zddc.access_federal.allow"),
		rego.Module("access_federal.rego", FederalRego),
	).PrepareForEval(context.Background())
	if err != nil {
		t.Fatalf("federal rego does not compile: %v", err)
	}
}
