package e2e_test

import (
	"testing"
)

func TestPermissions(t *testing.T) {
	alice := newTestEnv(t, "alice")
	bob := alice.withUser("bob")

	// Alice creates a node (gets has_ownership automatically).
	aliceNode := alice.parseNode(alice.mustAx("add", "Alice's secret", "--json"))
	aliceNodeID := aliceNode.ID

	// Resolve alice's user node ID.
	var aliceUserID string
	for _, u := range alice.parseNodes(alice.mustAx("list", "--type", "user", "--json")) {
		if u.Title == "alice" {
			aliceUserID = u.ID
		}
	}
	if aliceUserID == "" {
		t.Fatal("could not resolve alice's user node ID")
	}

	t.Run("NoAccess_CannotShow", func(t *testing.T) {
		_, err := bob.ax("show", aliceNodeID)
		if err == nil {
			t.Error("bob should not be able to show alice's node without access")
		}
	})

	t.Run("NoAccess_NotInList", func(t *testing.T) {
		bob.mustAx("add", "Bob's scratch", "--json")

		nodes := bob.parseNodes(bob.mustAx("list", "--json"))
		if _, ok := bob.findInList(nodes, aliceNodeID); ok {
			t.Error("alice's node should not appear in bob's list without access")
		}
	})

	t.Run("NoAccess_CannotUpdate", func(t *testing.T) {
		_, err := bob.ax("update", aliceNodeID, "--title", "hacked")
		if err == nil {
			t.Error("bob should not be able to update alice's node without access")
		}
	})

	// Resolve bob's user node ID. User nodes are globally readable.
	var bobUserID string
	for _, u := range alice.parseNodes(alice.mustAx("list", "--type", "user", "--json")) {
		if u.Title == "bob" {
			bobUserID = u.ID
		}
	}
	if bobUserID == "" {
		t.Fatal("could not resolve bob's user node ID")
	}

	t.Run("SelfEscalation_Denied", func(t *testing.T) {
		_, err := bob.ax("update", bobUserID, "--rel", "can_write:"+aliceNodeID)
		if err == nil {
			t.Error("bob should not be able to grant himself write access to alice's node")
		}
	})

	t.Run("ReadAccess_Grant", func(t *testing.T) {
		alice.mustAx("update", bobUserID, "--rel", "can_read:"+aliceNodeID)

		if _, err := bob.ax("show", aliceNodeID); err != nil {
			t.Error("bob should be able to show alice's node after read access granted")
		}
		nodes := bob.parseNodes(bob.mustAx("list", "--json"))
		if _, ok := bob.findInList(nodes, aliceNodeID); !ok {
			t.Error("alice's node should appear in bob's list after read access granted")
		}
	})

	t.Run("ReadAccess_CannotUpdate", func(t *testing.T) {
		_, err := bob.ax("update", aliceNodeID, "--title", "hacked with read access")
		if err == nil {
			t.Error("bob should not be able to update alice's node with only read access")
		}
	})

	t.Run("ReadAccess_CannotAddRelationToNode", func(t *testing.T) {
		bobLinkedID := bob.parseNode(bob.mustAx("add", "Bob's linked node", "--json")).ID
		_, err := bob.ax("update", bobLinkedID, "--rel", "related:"+aliceNodeID)
		if err == nil {
			t.Error("bob should not be able to create a relation to alice's node with only read access")
		}
	})

	t.Run("WriteAccess_Grant", func(t *testing.T) {
		alice.mustAx("update", bobUserID, "--rel", "can_write:"+aliceNodeID)

		out := bob.mustAx("update", aliceNodeID, "--title", "Bob modified this", "--json")
		n := bob.parseNode(out)
		if n.Title != "Bob modified this" {
			t.Errorf("expected 'Bob modified this', got %q", n.Title)
		}
	})

	t.Run("WriteAccess_CanAddRelationToNode", func(t *testing.T) {
		bobNode2 := bob.parseNode(bob.mustAx("add", "Bob's related node", "--json"))
		bob.mustAx("update", bobNode2.ID, "--rel", "related:"+aliceNodeID)
		out := bob.mustAx("show", bobNode2.ID, "--json")
		n := bob.parseNode(out)
		if !n.HasRelation("related", aliceNodeID) {
			t.Error("expected related relation after write access granted")
		}
	})

	t.Run("Ownership_DefaultOnCreate", func(t *testing.T) {
		userOut := alice.mustAx("show", aliceUserID, "--json")
		userNode := alice.parseNode(userOut)
		if !userNode.HasRelation("has_ownership", aliceUserID) {
			t.Errorf("expected user node to have self-ownership, got relations: %v", userNode.Relations)
		}
		// Nodes are now owned by the namespace they belong to, not directly by the creator.
		// Alice's default namespace is owned by alice, so she retains transitive ownership.
		if userNode.HasRelation("has_ownership", aliceNodeID) {
			t.Errorf("alice should not directly own her node (namespace owns it), got relations: %v", userNode.Relations)
		}
		// Find alice's default namespace and verify it owns the node.
		namespaces := alice.parseNodes(alice.mustAx("list", "--type", "namespace", "--json"))
		var aliceNsID string
		for _, ns := range namespaces {
			if ns.Title == "alice" {
				aliceNsID = ns.ID
			}
		}
		if aliceNsID == "" {
			t.Fatal("could not find alice's default namespace")
		}
		nsOut := alice.mustAx("show", aliceNsID, "--json")
		nsNode := alice.parseNode(nsOut)
		if !nsNode.HasRelation("has_ownership", aliceNodeID) {
			t.Errorf("expected alice's namespace to own her node, got relations: %v", nsNode.Relations)
		}
	})

	t.Run("Ownership_CascadeDelete", func(t *testing.T) {
		throwaway := alice.withUser("throwaway")
		child := throwaway.parseNode(throwaway.mustAx("add", "Child node", "--json"))

		var throwawayUserID string
		for _, u := range throwaway.parseNodes(throwaway.mustAx("list", "--type", "user", "--json")) {
			if u.Title == "throwaway" {
				throwawayUserID = u.ID
			}
		}
		if throwawayUserID == "" {
			t.Fatal("could not find throwaway user node")
		}

		throwaway.mustAx("del", throwawayUserID, "--force")

		_, err := throwaway.ax("show", child.ID)
		if err == nil {
			t.Error("child node should have been cascade-deleted when its owner was deleted")
		}
	})
}

func TestNamespaceExplicitCreate(t *testing.T) {
	env := newTestEnv(t, "testuser")

	nsNode := env.parseNode(env.mustAx("add", "myworkspace", "--type", "namespace", "--json"))

	users := env.parseNodes(env.mustAx("list", "--type", "user", "--json"))
	var userNode *NodeResponse
	for i := range users {
		if users[i].Title == "testuser" {
			userNode = &users[i]
			break
		}
	}
	if userNode == nil {
		t.Fatal("could not find testuser node")
	}
	if !userNode.HasRelation("has_ownership", nsNode.ID) {
		t.Errorf("expected creator to have has_ownership on new namespace, got relations: %v", userNode.Relations)
	}

	env.mustAx("add", "task in workspace", "--namespace", "myworkspace", "--json")
	listed := env.parseNodes(env.mustAx("list", "--namespace", "myworkspace", "--json"))
	if len(listed) == 0 {
		t.Error("expected to list nodes in newly created namespace")
	}
}
