package policy

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

	"github.com/adrg/xdg"
	"git.mleku.dev/mleku/nostr/encoders/hex"
)

// setupTestPolicy creates a policy manager with a temporary config file.
// Returns the policy and a cleanup function.
func setupTestPolicy(t *testing.T, appName string) (*P, func()) {
	t.Helper()

	// Create config directory at XDG path
	configDir := filepath.Join(xdg.ConfigHome, appName)
	if err := os.MkdirAll(configDir, 0755); err != nil {
		t.Fatalf("Failed to create config dir: %v", err)
	}

	// Create default policy.json
	configPath := filepath.Join(configDir, "policy.json")
	defaultPolicy := []byte(`{"default_policy": "allow"}`)
	if err := os.WriteFile(configPath, defaultPolicy, 0644); err != nil {
		t.Fatalf("Failed to write policy file: %v", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

	policy := NewWithManager(ctx, appName, true, "")
	if policy == nil {
		cancel()
		os.RemoveAll(configDir)
		t.Fatal("Failed to create policy manager")
	}

	cleanup := func() {
		cancel()
		os.RemoveAll(configDir)
	}

	return policy, cleanup
}

// TestIsPolicyAdmin tests the IsPolicyAdmin method
func TestIsPolicyAdmin(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-policy-admin")
	defer cleanup()

	// Set up policy with admins
	admin1Hex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	admin2Hex := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"
	nonAdminHex := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

	policyJSON := []byte(`{
		"policy_admins": [
			"` + admin1Hex + `",
			"` + admin2Hex + `"
		]
	}`)

	tmpDir := t.TempDir()
	if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
		t.Fatalf("Failed to reload policy: %v", err)
	}

	// Convert hex to bytes for testing
	admin1Bin, _ := hex.Dec(admin1Hex)
	admin2Bin, _ := hex.Dec(admin2Hex)
	nonAdminBin, _ := hex.Dec(nonAdminHex)

	tests := []struct {
		name     string
		pubkey   []byte
		expected bool
	}{
		{
			name:     "first admin is recognized",
			pubkey:   admin1Bin,
			expected: true,
		},
		{
			name:     "second admin is recognized",
			pubkey:   admin2Bin,
			expected: true,
		},
		{
			name:     "non-admin is not recognized",
			pubkey:   nonAdminBin,
			expected: false,
		},
		{
			name:     "nil pubkey returns false",
			pubkey:   nil,
			expected: false,
		},
		{
			name:     "empty pubkey returns false",
			pubkey:   []byte{},
			expected: false,
		},
		{
			name:     "wrong length pubkey returns false",
			pubkey:   []byte{0x01, 0x02, 0x03},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := policy.IsPolicyAdmin(tt.pubkey)
			if result != tt.expected {
				t.Errorf("IsPolicyAdmin() = %v, expected %v", result, tt.expected)
			}
		})
	}
}

// TestIsPolicyFollow tests the IsPolicyFollow method
func TestIsPolicyFollow(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-policy-follow")
	defer cleanup()

	// Set up some follows
	follow1Hex := "1111111111111111111111111111111111111111111111111111111111111111"
	follow2Hex := "2222222222222222222222222222222222222222222222222222222222222222"
	nonFollowHex := "3333333333333333333333333333333333333333333333333333333333333333"

	follow1Bin, _ := hex.Dec(follow1Hex)
	follow2Bin, _ := hex.Dec(follow2Hex)
	nonFollowBin, _ := hex.Dec(nonFollowHex)

	// Update policy follows directly
	policy.UpdatePolicyFollows([][]byte{follow1Bin, follow2Bin})

	tests := []struct {
		name     string
		pubkey   []byte
		expected bool
	}{
		{
			name:     "first follow is recognized",
			pubkey:   follow1Bin,
			expected: true,
		},
		{
			name:     "second follow is recognized",
			pubkey:   follow2Bin,
			expected: true,
		},
		{
			name:     "non-follow is not recognized",
			pubkey:   nonFollowBin,
			expected: false,
		},
		{
			name:     "nil pubkey returns false",
			pubkey:   nil,
			expected: false,
		},
		{
			name:     "empty pubkey returns false",
			pubkey:   []byte{},
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := policy.IsPolicyFollow(tt.pubkey)
			if result != tt.expected {
				t.Errorf("IsPolicyFollow() = %v, expected %v", result, tt.expected)
			}
		})
	}
}

// TestUpdatePolicyFollows tests the UpdatePolicyFollows method
func TestUpdatePolicyFollows(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-update-follows")
	defer cleanup()

	// Initially no follows
	testPubkey, _ := hex.Dec("1111111111111111111111111111111111111111111111111111111111111111")
	if policy.IsPolicyFollow(testPubkey) {
		t.Error("Expected no follows initially")
	}

	// Add follows
	follows := [][]byte{testPubkey}
	policy.UpdatePolicyFollows(follows)

	if !policy.IsPolicyFollow(testPubkey) {
		t.Error("Expected pubkey to be a follow after update")
	}

	// Update with empty list
	policy.UpdatePolicyFollows([][]byte{})
	if policy.IsPolicyFollow(testPubkey) {
		t.Error("Expected pubkey to not be a follow after clearing")
	}

	// Update with nil
	policy.UpdatePolicyFollows(nil)
	if policy.IsPolicyFollow(testPubkey) {
		t.Error("Expected pubkey to not be a follow after nil update")
	}
}

// TestIsPolicyFollowWhitelistEnabled tests the IsPolicyFollowWhitelistEnabled method
func TestIsPolicyFollowWhitelistEnabled(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-whitelist-enabled")
	defer cleanup()

	tmpDir := t.TempDir()

	// Test with disabled
	policyJSON := []byte(`{"policy_follow_whitelist_enabled": false}`)
	if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
		t.Fatalf("Failed to reload policy: %v", err)
	}

	if policy.IsPolicyFollowWhitelistEnabled() {
		t.Error("Expected follow whitelist to be disabled")
	}

	// Test with enabled
	policyJSON = []byte(`{"policy_follow_whitelist_enabled": true}`)
	if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
		t.Fatalf("Failed to reload policy: %v", err)
	}

	if !policy.IsPolicyFollowWhitelistEnabled() {
		t.Error("Expected follow whitelist to be enabled")
	}
}

// TestGetPolicyAdminsBin tests the GetPolicyAdminsBin method
func TestGetPolicyAdminsBin(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-get-admins-bin")
	defer cleanup()

	admin1Hex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	admin2Hex := "fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210"

	policyJSON := []byte(`{
		"policy_admins": ["` + admin1Hex + `", "` + admin2Hex + `"]
	}`)

	tmpDir := t.TempDir()
	if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
		t.Fatalf("Failed to reload policy: %v", err)
	}

	admins := policy.GetPolicyAdminsBin()
	if len(admins) != 2 {
		t.Errorf("Expected 2 admins, got %d", len(admins))
	}

	// Verify it's a copy (modification shouldn't affect original)
	if len(admins) > 0 {
		admins[0][0] = 0xFF
		originalAdmins := policy.GetPolicyAdminsBin()
		if originalAdmins[0][0] == 0xFF {
			t.Error("GetPolicyAdminsBin should return a copy, not the original slice")
		}
	}
}

// TestFollowListConcurrency tests concurrent access to follow list
func TestFollowListConcurrency(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-concurrency")
	defer cleanup()

	testPubkey, _ := hex.Dec("1111111111111111111111111111111111111111111111111111111111111111")

	// Run concurrent reads and writes
	done := make(chan bool)
	for i := 0; i < 10; i++ {
		go func() {
			for j := 0; j < 100; j++ {
				policy.UpdatePolicyFollows([][]byte{testPubkey})
				_ = policy.IsPolicyFollow(testPubkey)
				_ = policy.IsPolicyAdmin(testPubkey)
			}
			done <- true
		}()
	}

	// Wait for all goroutines
	for i := 0; i < 10; i++ {
		<-done
	}
}

// TestPolicyAdminAndFollowInteraction tests the interaction between admin and follow checks
func TestPolicyAdminAndFollowInteraction(t *testing.T) {
	policy, cleanup := setupTestPolicy(t, "test-admin-follow-interaction")
	defer cleanup()

	// An admin who is also followed
	adminHex := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
	adminBin, _ := hex.Dec(adminHex)

	policyJSON := []byte(`{
		"policy_admins": ["` + adminHex + `"],
		"policy_follow_whitelist_enabled": true
	}`)

	tmpDir := t.TempDir()
	if err := policy.Reload(policyJSON, tmpDir+"/policy.json"); err != nil {
		t.Fatalf("Failed to reload policy: %v", err)
	}

	// Admin should be recognized as admin
	if !policy.IsPolicyAdmin(adminBin) {
		t.Error("Expected admin to be recognized as admin")
	}

	// Admin is not automatically a follow
	if policy.IsPolicyFollow(adminBin) {
		t.Error("Admin should not automatically be a follow")
	}

	// Now add admin as a follow
	policy.UpdatePolicyFollows([][]byte{adminBin})

	// Should be both admin and follow
	if !policy.IsPolicyAdmin(adminBin) {
		t.Error("Expected admin to still be recognized as admin")
	}
	if !policy.IsPolicyFollow(adminBin) {
		t.Error("Expected admin to now be recognized as follow")
	}
}
