package policy

import (
	"testing"
	"time"

	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
	"lol.mleku.dev/chk"
)

// =============================================================================
// Default-Permissive Access Control Tests
// =============================================================================

// TestDefaultPermissiveRead tests that read access is allowed by default
// when no read restrictions are configured.
func TestDefaultPermissiveRead(t *testing.T) {
	// No read restrictions configured
	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"1": {
				"description": "No read restrictions"
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	authorSigner, authorPubkey := generateTestKeypair(t)
	_, readerPubkey := generateTestKeypair(t)
	_, randomPubkey := generateTestKeypair(t)

	ev := createTestEvent(t, authorSigner, "test content", 1)

	tests := []struct {
		name        string
		pubkey      []byte
		expectAllow bool
	}{
		{
			name:        "author can read (default permissive)",
			pubkey:      authorPubkey,
			expectAllow: true,
		},
		{
			name:        "reader can read (default permissive)",
			pubkey:      readerPubkey,
			expectAllow: true,
		},
		{
			name:        "random user can read (default permissive)",
			pubkey:      randomPubkey,
			expectAllow: true,
		},
		{
			name:        "nil pubkey can read (default permissive)",
			pubkey:      nil,
			expectAllow: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
			if err != nil {
				t.Fatalf("CheckPolicy error: %v", err)
			}
			if allowed != tt.expectAllow {
				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
			}
		})
	}
}

// TestDefaultPermissiveWrite tests that write access is allowed by default
// when no write restrictions are configured.
func TestDefaultPermissiveWrite(t *testing.T) {
	// No write restrictions configured
	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"1": {
				"description": "No write restrictions"
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	writerSigner, writerPubkey := generateTestKeypair(t)
	_, randomPubkey := generateTestKeypair(t)

	tests := []struct {
		name        string
		signer      *p8k.Signer
		pubkey      []byte
		expectAllow bool
	}{
		{
			name:        "writer can write (default permissive)",
			signer:      writerSigner,
			pubkey:      writerPubkey,
			expectAllow: true,
		},
		{
			name:        "random user can write (default permissive)",
			signer:      writerSigner,
			pubkey:      randomPubkey,
			expectAllow: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ev := createTestEvent(t, tt.signer, "test content", 1)
			allowed, err := policy.CheckPolicy("write", ev, tt.pubkey, "127.0.0.1")
			if err != nil {
				t.Fatalf("CheckPolicy error: %v", err)
			}
			if allowed != tt.expectAllow {
				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
			}
		})
	}
}

// TestReadFollowsWhitelist tests the read_follows_whitelist field.
func TestReadFollowsWhitelist(t *testing.T) {
	_, curatorPubkey := generateTestKeypair(t)
	_, followedPubkey := generateTestKeypair(t)
	_, unfollowedPubkey := generateTestKeypair(t)
	authorSigner, authorPubkey := generateTestKeypair(t)

	curatorHex := hex.Enc(curatorPubkey)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"1": {
				"description": "Only curator follows can read",
				"read_follows_whitelist": ["` + curatorHex + `"]
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	// Simulate loading curator's follows (includes followed user and curator themselves)
	policy.UpdateRuleReadFollowsWhitelist(1, [][]byte{followedPubkey})

	ev := createTestEvent(t, authorSigner, "test content", 1)

	tests := []struct {
		name        string
		pubkey      []byte
		expectAllow bool
	}{
		{
			name:        "curator can read (is in whitelist pubkeys)",
			pubkey:      curatorPubkey,
			expectAllow: true,
		},
		{
			name:        "followed user can read",
			pubkey:      followedPubkey,
			expectAllow: true,
		},
		{
			name:        "unfollowed user denied",
			pubkey:      unfollowedPubkey,
			expectAllow: false,
		},
		{
			name:        "author cannot read (not in follows)",
			pubkey:      authorPubkey,
			expectAllow: false,
		},
		{
			name:        "nil pubkey denied",
			pubkey:      nil,
			expectAllow: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
			if err != nil {
				t.Fatalf("CheckPolicy error: %v", err)
			}
			if allowed != tt.expectAllow {
				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
			}
		})
	}

	// Verify write is still default-permissive (no write restriction)
	t.Run("write is still default permissive", func(t *testing.T) {
		allowed, err := policy.CheckPolicy("write", ev, unfollowedPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected write to be allowed (no write restriction)")
		}
	})
}

// TestWriteFollowsWhitelist tests the write_follows_whitelist field.
func TestWriteFollowsWhitelist(t *testing.T) {
	moderatorSigner, moderatorPubkey := generateTestKeypair(t)
	followedSigner, followedPubkey := generateTestKeypair(t)
	unfollowedSigner, unfollowedPubkey := generateTestKeypair(t)

	moderatorHex := hex.Enc(moderatorPubkey)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"1": {
				"description": "Only moderator follows can write",
				"write_follows_whitelist": ["` + moderatorHex + `"]
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	// Simulate loading moderator's follows
	policy.UpdateRuleWriteFollowsWhitelist(1, [][]byte{followedPubkey})

	tests := []struct {
		name        string
		signer      *p8k.Signer
		pubkey      []byte
		expectAllow bool
	}{
		{
			name:        "moderator can write (is in whitelist pubkeys)",
			signer:      moderatorSigner,
			pubkey:      moderatorPubkey,
			expectAllow: true,
		},
		{
			name:        "followed user can write",
			signer:      followedSigner,
			pubkey:      followedPubkey,
			expectAllow: true,
		},
		{
			name:        "unfollowed user denied",
			signer:      unfollowedSigner,
			pubkey:      unfollowedPubkey,
			expectAllow: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ev := createTestEvent(t, tt.signer, "test content", 1)
			allowed, err := policy.CheckPolicy("write", ev, tt.pubkey, "127.0.0.1")
			if err != nil {
				t.Fatalf("CheckPolicy error: %v", err)
			}
			if allowed != tt.expectAllow {
				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
			}
		})
	}

	// Verify read is still default-permissive (no read restriction)
	t.Run("read is still default permissive", func(t *testing.T) {
		ev := createTestEvent(t, unfollowedSigner, "test content", 1)
		allowed, err := policy.CheckPolicy("read", ev, unfollowedPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected read to be allowed (no read restriction)")
		}
	})
}

// TestGlobalReadFollowsWhitelist tests read_follows_whitelist in global rule.
func TestGlobalReadFollowsWhitelist(t *testing.T) {
	_, curatorPubkey := generateTestKeypair(t)
	_, followedPubkey := generateTestKeypair(t)
	_, unfollowedPubkey := generateTestKeypair(t)
	authorSigner, _ := generateTestKeypair(t)

	curatorHex := hex.Enc(curatorPubkey)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"global": {
			"description": "Global read follows whitelist",
			"read_follows_whitelist": ["` + curatorHex + `"]
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	// Update global read follows whitelist
	policy.UpdateGlobalReadFollowsWhitelist([][]byte{followedPubkey})

	// Test with kind 1
	t.Run("kind 1", func(t *testing.T) {
		ev := createTestEvent(t, authorSigner, "test content", 1)

		// Followed user can read
		allowed, err := policy.CheckPolicy("read", ev, followedPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected followed user to be allowed to read")
		}

		// Unfollowed user denied
		allowed, err = policy.CheckPolicy("read", ev, unfollowedPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if allowed {
			t.Error("Expected unfollowed user to be denied")
		}
	})
}

// TestGlobalWriteFollowsWhitelist tests write_follows_whitelist in global rule.
func TestGlobalWriteFollowsWhitelist(t *testing.T) {
	_, moderatorPubkey := generateTestKeypair(t)
	followedSigner, followedPubkey := generateTestKeypair(t)
	unfollowedSigner, unfollowedPubkey := generateTestKeypair(t)

	moderatorHex := hex.Enc(moderatorPubkey)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"global": {
			"description": "Global write follows whitelist",
			"write_follows_whitelist": ["` + moderatorHex + `"]
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	// Update global write follows whitelist
	policy.UpdateGlobalWriteFollowsWhitelist([][]byte{followedPubkey})

	// Test with kind 1
	t.Run("kind 1", func(t *testing.T) {
		// Followed user can write
		ev := createTestEvent(t, followedSigner, "test content", 1)
		allowed, err := policy.CheckPolicy("write", ev, followedPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected followed user to be allowed to write")
		}

		// Unfollowed user denied
		ev = createTestEvent(t, unfollowedSigner, "test content", 1)
		allowed, err = policy.CheckPolicy("write", ev, unfollowedPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if allowed {
			t.Error("Expected unfollowed user to be denied")
		}
	})
}

// TestPrivilegedOnlyAppliesToReadDP tests that privileged only affects read access.
func TestPrivilegedOnlyAppliesToReadDP(t *testing.T) {
	authorSigner, authorPubkey := generateTestKeypair(t)
	_, recipientPubkey := generateTestKeypair(t)
	thirdPartySigner, thirdPartyPubkey := generateTestKeypair(t)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"4": {
				"description": "Encrypted DMs - privileged",
				"privileged": true
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	// Create event with p-tag for recipient
	ev := event.New()
	ev.Kind = 4
	ev.Content = []byte("encrypted content")
	ev.CreatedAt = time.Now().Unix()
	ev.Tags = tag.NewS()
	pTag := tag.NewFromAny("p", hex.Enc(recipientPubkey))
	ev.Tags.Append(pTag)
	if err := ev.Sign(authorSigner); chk.E(err) {
		t.Fatalf("Failed to sign event: %v", err)
	}

	// READ tests
	t.Run("author can read", func(t *testing.T) {
		allowed, err := policy.CheckPolicy("read", ev, authorPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected author to be allowed to read")
		}
	})

	t.Run("recipient can read", func(t *testing.T) {
		allowed, err := policy.CheckPolicy("read", ev, recipientPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected recipient to be allowed to read")
		}
	})

	t.Run("third party cannot read", func(t *testing.T) {
		allowed, err := policy.CheckPolicy("read", ev, thirdPartyPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if allowed {
			t.Error("Expected third party to be denied read access")
		}
	})

	// WRITE tests - privileged should NOT affect write
	t.Run("third party CAN write (privileged doesn't affect write)", func(t *testing.T) {
		ev := createTestEvent(t, thirdPartySigner, "test content", 4)
		allowed, err := policy.CheckPolicy("write", ev, thirdPartyPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("CheckPolicy error: %v", err)
		}
		if !allowed {
			t.Error("Expected third party to be allowed to write (privileged doesn't restrict write)")
		}
	})
}

// TestCombinedReadWriteFollowsWhitelists tests using both whitelists on same rule.
func TestCombinedReadWriteFollowsWhitelists(t *testing.T) {
	_, curatorPubkey := generateTestKeypair(t)
	_, moderatorPubkey := generateTestKeypair(t)
	readerSigner, readerPubkey := generateTestKeypair(t)
	writerSigner, writerPubkey := generateTestKeypair(t)
	_, outsiderPubkey := generateTestKeypair(t)

	curatorHex := hex.Enc(curatorPubkey)
	moderatorHex := hex.Enc(moderatorPubkey)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"30023": {
				"description": "Articles - different read/write follows",
				"read_follows_whitelist": ["` + curatorHex + `"],
				"write_follows_whitelist": ["` + moderatorHex + `"]
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	// Curator follows reader, moderator follows writer
	policy.UpdateRuleReadFollowsWhitelist(30023, [][]byte{readerPubkey})
	policy.UpdateRuleWriteFollowsWhitelist(30023, [][]byte{writerPubkey})

	tests := []struct {
		name        string
		access      string
		signer      *p8k.Signer
		pubkey      []byte
		expectAllow bool
	}{
		// Read tests
		{
			name:        "reader can read",
			access:      "read",
			signer:      readerSigner,
			pubkey:      readerPubkey,
			expectAllow: true,
		},
		{
			name:        "writer cannot read (not in read follows)",
			access:      "read",
			signer:      writerSigner,
			pubkey:      writerPubkey,
			expectAllow: false,
		},
		{
			name:        "outsider cannot read",
			access:      "read",
			signer:      readerSigner,
			pubkey:      outsiderPubkey,
			expectAllow: false,
		},
		// Write tests
		{
			name:        "writer can write",
			access:      "write",
			signer:      writerSigner,
			pubkey:      writerPubkey,
			expectAllow: true,
		},
		{
			name:        "reader cannot write (not in write follows)",
			access:      "write",
			signer:      readerSigner,
			pubkey:      readerPubkey,
			expectAllow: false,
		},
		{
			name:        "outsider cannot write",
			access:      "write",
			signer:      readerSigner,
			pubkey:      outsiderPubkey,
			expectAllow: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ev := createTestEvent(t, tt.signer, "test content", 30023)
			allowed, err := policy.CheckPolicy(tt.access, ev, tt.pubkey, "127.0.0.1")
			if err != nil {
				t.Fatalf("CheckPolicy error: %v", err)
			}
			if allowed != tt.expectAllow {
				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
			}
		})
	}
}

// TestReadAllowWithReadFollowsWhitelist tests combining read_allow and read_follows_whitelist.
func TestReadAllowWithReadFollowsWhitelist(t *testing.T) {
	_, curatorPubkey := generateTestKeypair(t)
	_, followedPubkey := generateTestKeypair(t)
	_, explicitPubkey := generateTestKeypair(t)
	_, outsiderPubkey := generateTestKeypair(t)
	authorSigner, _ := generateTestKeypair(t)

	curatorHex := hex.Enc(curatorPubkey)
	explicitHex := hex.Enc(explicitPubkey)

	policyJSON := []byte(`{
		"default_policy": "deny",
		"rules": {
			"1": {
				"description": "Read via follows OR explicit allow",
				"read_follows_whitelist": ["` + curatorHex + `"],
				"read_allow": ["` + explicitHex + `"]
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	policy.UpdateRuleReadFollowsWhitelist(1, [][]byte{followedPubkey})

	ev := createTestEvent(t, authorSigner, "test content", 1)

	tests := []struct {
		name        string
		pubkey      []byte
		expectAllow bool
	}{
		{
			name:        "followed user can read",
			pubkey:      followedPubkey,
			expectAllow: true,
		},
		{
			name:        "explicit allow user can read",
			pubkey:      explicitPubkey,
			expectAllow: true,
		},
		{
			name:        "curator can read (is whitelist pubkey)",
			pubkey:      curatorPubkey,
			expectAllow: true,
		},
		{
			name:        "outsider denied",
			pubkey:      outsiderPubkey,
			expectAllow: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			allowed, err := policy.CheckPolicy("read", ev, tt.pubkey, "127.0.0.1")
			if err != nil {
				t.Fatalf("CheckPolicy error: %v", err)
			}
			if allowed != tt.expectAllow {
				t.Errorf("CheckPolicy() = %v, expected %v", allowed, tt.expectAllow)
			}
		})
	}
}

// TestGetAllFollowsWhitelistPubkeysDP tests the combined pubkey retrieval.
func TestGetAllFollowsWhitelistPubkeysDP(t *testing.T) {
	read1 := "1111111111111111111111111111111111111111111111111111111111111111"
	read2 := "2222222222222222222222222222222222222222222222222222222222222222"
	write1 := "3333333333333333333333333333333333333333333333333333333333333333"
	legacy := "4444444444444444444444444444444444444444444444444444444444444444"

	policyJSON := []byte(`{
		"default_policy": "allow",
		"global": {
			"read_follows_whitelist": ["` + read1 + `"],
			"write_follows_whitelist": ["` + write1 + `"]
		},
		"rules": {
			"1": {
				"read_follows_whitelist": ["` + read2 + `"],
				"follows_whitelist_admins": ["` + legacy + `"]
			}
		}
	}`)

	policy, err := New(policyJSON)
	if err != nil {
		t.Fatalf("Failed to create policy: %v", err)
	}

	allPubkeys := policy.GetAllFollowsWhitelistPubkeys()
	if len(allPubkeys) != 4 {
		t.Errorf("Expected 4 unique pubkeys, got %d", len(allPubkeys))
	}

	// Check each is present
	pubkeySet := make(map[string]bool)
	for _, pk := range allPubkeys {
		pubkeySet[pk] = true
	}

	expected := []string{read1, read2, write1, legacy}
	for _, exp := range expected {
		if !pubkeySet[exp] {
			t.Errorf("Expected pubkey %s not found", exp)
		}
	}
}
