package handler

// Layer 2 — the SHIPPED DEFAULT POLICY contract.
//
// This is the executable truth table for the embedded defaults
// (internal/zddc/defaults/): role × canonical-path × verb → allow/deny.
// It pins the document-control access model so a change to the defaults — OR to
// the engine that resolves them — can't silently alter who-can-do-what. (When
// the defaults later move into a project-root .zddc.zip of per-depth .zddc
// files, this test is unchanged: it asserts EFFECTIVE policy, not where the
// bytes live.)
//
// Two layers, deliberately separate:
//   - Layer 1 (engine follows whatever policy says): policy.TestInternalDecider_
//     CascadeScenarios + internal/zddc/{acl,roles,worm}_test.go (synthetic
//     policies) + internal/policy/parity_test.go (InternalDecider ↔ OPA).
//   - Layer 2 (the shipped defaults are correct): THIS file.
//
// Decisions go through the same decider the server uses (InternalDecider, which
// applies the cascade + WORM mask + active-admin bypass), evaluated at the
// target's logical parent — mirroring authorizeAction. The HTTP plumbing that
// chooses that path is covered separately by the auth_invariants tests.

import (
	"context"
	"path/filepath"
	"testing"

	"codeberg.org/VARASYS/ZDDC/zddc/internal/config"
	"codeberg.org/VARASYS/ZDDC/zddc/internal/policy"
	"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
)

// defaultsMatrixFixture is a minimal operator deployment: it only populates the
// three standard roles (which the embedded defaults ship empty) plus one admin,
// and registers party Acme (party_source: ssr gates the peers). Every grant in
// the matrix below therefore comes from the embedded defaults, not the fixture.
func defaultsMatrixFixture(t *testing.T) config.Config {
	t.Helper()
	root := t.TempDir()
	mustWriteHelper(t, filepath.Join(root, ".zddc"),
		"admins:\n  - admin@x\n"+
			"roles:\n"+
			"  document_controller:\n    members: [dc@x]\n"+
			"  project_team:\n    members: [team@x]\n"+
			"  observer:\n    members: [obs@x]\n")
	mustWriteHelper(t, filepath.Join(root, "Proj/ssr/Acme.yaml"), "kind: SSR\n")
	zddc.InvalidateCache(root)
	return config.Config{Root: root, EmailHeader: "X-Auth-Request-Email", MaxWriteBytes: 64 * 1024}
}

// canDo reports whether <email> (elevated?) may perform <action> on content in
// <dir> — the chain is resolved at <dir> (the logical parent of the child being
// acted on) and routed through the internal decider, exactly as the server's
// authorizeAction does for a create/write/delete/read.
func canDo(t *testing.T, cfg config.Config, email string, elevated bool, dir, action string) bool {
	t.Helper()
	p := zddc.Principal{Email: email, Elevated: elevated}
	chain, err := zddc.EffectivePolicy(cfg.Root, filepath.Join(cfg.Root, filepath.FromSlash(dir)))
	if err != nil {
		t.Fatalf("EffectivePolicy(%s): %v", dir, err)
	}
	allowed, _ := policy.AllowActionFromChainP(
		context.Background(), &policy.InternalDecider{}, chain, p, "/"+dir+"/probe", action)
	return allowed
}

func TestDefaultPolicyMatrix(t *testing.T) {
	cfg := defaultsMatrixFixture(t)
	const (
		R = policy.ActionRead
		W = policy.ActionWrite
		C = policy.ActionCreate
		D = policy.ActionDelete
	)
	cases := []struct {
		note   string
		who    string
		elev   bool
		dir    string
		action string
		want   bool
	}{
		// ── Project root: standard peers only; no create for anyone ──────────
		{"team: read project root", "team@x", false, "Proj", R, true},
		{"observer: read project root", "obs@x", false, "Proj", R, true},
		{"team: NO create at project root", "team@x", false, "Proj", C, false},
		{"DC: NO create at project root", "dc@x", false, "Proj", C, false},

		// ── working/<party>: DC rwcda, team cr, observer r ───────────────────
		{"DC: create in working", "dc@x", false, "Proj/working/Acme", C, true},
		{"team: create in working", "team@x", false, "Proj/working/Acme", C, true},
		{"team: read working", "team@x", false, "Proj/working/Acme", R, true},
		{"observer: read working", "obs@x", false, "Proj/working/Acme", R, true},
		{"observer: NO create in working", "obs@x", false, "Proj/working/Acme", C, false},
		// nested under working — the path the authorizeAction bug denied
		{"DC: create nested in working", "dc@x", false, "Proj/working/Acme/sub", C, true},
		{"team: create nested in working", "team@x", false, "Proj/working/Acme/sub", C, true},

		// ── staging / reviewing: team cr ─────────────────────────────────────
		{"team: create in staging", "team@x", false, "Proj/staging/Acme", C, true},
		{"team: create in reviewing", "team@x", false, "Proj/reviewing/Acme", C, true},

		// ── incoming: DC rwcd, team read-only ────────────────────────────────
		{"DC: create in incoming", "dc@x", false, "Proj/incoming/Acme", C, true},
		{"team: NO create in incoming", "team@x", false, "Proj/incoming/Acme", C, false},
		{"team: read incoming", "team@x", false, "Proj/incoming/Acme", R, true},

		// ── ssr (party registry): DC rwc, team read-only ─────────────────────
		{"DC: register party (create in ssr)", "dc@x", false, "Proj/ssr", C, true},
		{"team: NO create in ssr", "team@x", false, "Proj/ssr", C, false},
		{"team: read ssr", "team@x", false, "Proj/ssr", R, true},

		// ── mdl / rsk registers: DC rwcd, team rwc (no delete), observer r ───
		{"DC: create mdl row", "dc@x", false, "Proj/mdl/Acme", C, true},
		{"DC: delete mdl row", "dc@x", false, "Proj/mdl/Acme", D, true},
		{"team: create mdl row", "team@x", false, "Proj/mdl/Acme", C, true},
		{"team: edit mdl row", "team@x", false, "Proj/mdl/Acme", W, true},
		{"team: NO delete mdl row", "team@x", false, "Proj/mdl/Acme", D, false},
		{"observer: NO create mdl row", "obs@x", false, "Proj/mdl/Acme", C, false},
		{"team: create rsk row", "team@x", false, "Proj/rsk/Acme", C, true},
		{"team: edit rsk row", "team@x", false, "Proj/rsk/Acme", W, true},
		{"team: NO delete rsk row", "team@x", false, "Proj/rsk/Acme", D, false},

		// ── archive WORM: DC create-once, no write/delete; others read ───────
		{"DC: worm-create in received", "dc@x", false, "Proj/archive/Acme/received", C, true},
		{"DC: NO write in WORM received", "dc@x", false, "Proj/archive/Acme/received", W, false},
		{"DC: NO delete in WORM issued", "dc@x", false, "Proj/archive/Acme/issued", D, false},
		{"team: NO create in archive", "team@x", false, "Proj/archive/Acme/issued", C, false},
		{"team: read archive", "team@x", false, "Proj/archive/Acme/issued", R, true},

		// ── Elevated admin: full bypass (the human escape hatch) ─────────────
		{"elevated admin: bypass WORM write", "admin@x", true, "Proj/archive/Acme/issued", W, true},
		{"elevated admin: create in working", "admin@x", true, "Proj/working/Acme", C, true},

		// ── Un-elevated admin: NO bypass; not in any role → no grant ─────────
		{"un-elevated admin: NO WORM bypass", "admin@x", false, "Proj/archive/Acme/issued", W, false},
		{"un-elevated admin: NO create in working", "admin@x", false, "Proj/working/Acme", C, false},

		// ── Anonymous: nothing (a .zddc exists → no public default) ──────────
		{"anon: NO read working", "", false, "Proj/working/Acme", R, false},
		{"anon: NO create working", "", false, "Proj/working/Acme", C, false},
	}

	for _, tc := range cases {
		got := canDo(t, cfg, tc.who, tc.elev, tc.dir, tc.action)
		if got != tc.want {
			t.Errorf("%s — canDo(%q, elevated=%v, %s, %q) = %v, want %v",
				tc.note, tc.who, tc.elev, tc.dir, tc.action, got, tc.want)
		}
	}
}
