package policy

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"lol.mleku.dev/log"
)

// TestBugReproduction_Kind1AllowedWithWhitelist4678 reproduces the reported bug
// where kind 1 events are being accepted even though only kind 4678 is in the whitelist.
func TestBugReproduction_Kind1AllowedWithWhitelist4678(t *testing.T) {
	testSigner, testPubkey := generateTestKeypair(t)

	// Create policy matching the production configuration
	policyJSON := `{
		"kind": { "whitelist": [4678] },
		"rules": {
			"4678": {
				"description": "Zenotp events",
				"script": "policy.sh"
			}
		}
	}`

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

	t.Run("Kind 1 should be REJECTED (not in whitelist)", func(t *testing.T) {
		event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
		allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if allowed {
			t.Errorf("BUG REPRODUCED: Kind 1 event was ALLOWED but should be REJECTED (only kind 4678 is whitelisted)")
			t.Logf("Policy whitelist: %v", policy.Kind.Whitelist)
			t.Logf("Policy rules: %v", policy.rules)
			t.Logf("Default policy: %s", policy.DefaultPolicy)
		}
	})

	t.Run("Kind 4678 should be ALLOWED (in whitelist)", func(t *testing.T) {
		event := createTestEvent(t, testSigner, "Zenotp event", 4678)
		allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if !allowed {
			t.Error("Kind 4678 should be ALLOWED (in whitelist)")
		}
	})
}

// TestBugReproduction_WithPolicyManager tests with a full policy manager setup
// to match production environment more closely
func TestBugReproduction_WithPolicyManager(t *testing.T) {
	testSigner, testPubkey := generateTestKeypair(t)

	// Create a temporary config directory
	tmpDir := t.TempDir()
	configDir := filepath.Join(tmpDir, "ORLY")
	if err := os.MkdirAll(configDir, 0755); err != nil {
		t.Fatalf("Failed to create config dir: %v", err)
	}

	// Write policy configuration matching production
	policyConfig := map[string]interface{}{
		"kind": map[string]interface{}{
			"whitelist": []int{4678},
		},
		"rules": map[string]interface{}{
			"4678": map[string]interface{}{
				"description": "Zenotp events",
				"script":      "policy.sh",
			},
		},
	}

	policyJSON, err := json.MarshalIndent(policyConfig, "", "  ")
	if err != nil {
		t.Fatalf("Failed to marshal policy JSON: %v", err)
	}

	policyPath := filepath.Join(configDir, "policy.json")
	if err := os.WriteFile(policyPath, policyJSON, 0644); err != nil {
		t.Fatalf("Failed to write policy file: %v", err)
	}

	// Create policy with manager (enabled)
	ctx := context.Background()
	policy := NewWithManager(ctx, "ORLY", true, "")

	// Load policy from file
	if err := policy.LoadFromFile(policyPath); err != nil {
		t.Fatalf("Failed to load policy from file: %v", err)
	}

	t.Run("Kind 1 should be REJECTED with PolicyManager", func(t *testing.T) {
		event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
		allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if allowed {
			t.Errorf("BUG REPRODUCED: Kind 1 event was ALLOWED but should be REJECTED")
			t.Logf("Policy whitelist: %v", policy.Kind.Whitelist)
			t.Logf("Policy rules: %v", policy.rules)
			t.Logf("Default policy: %s", policy.DefaultPolicy)
			t.Logf("Manager enabled: %v", policy.manager.IsEnabled())
		}
	})

	t.Run("Kind 4678 should be ALLOWED with PolicyManager", func(t *testing.T) {
		event := createTestEvent(t, testSigner, "Zenotp event", 4678)
		allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if !allowed {
			t.Error("Kind 4678 should be ALLOWED (in whitelist)")
		}
	})

	// Clean up
	if policy.manager != nil {
		policy.manager.Shutdown()
	}
}

// TestBugReproduction_DebugPolicyFlow adds verbose logging to debug the policy flow
func TestBugReproduction_DebugPolicyFlow(t *testing.T) {
	testSigner, testPubkey := generateTestKeypair(t)

	policyJSON := `{
		"kind": { "whitelist": [4678] },
		"rules": {
			"4678": {
				"description": "Zenotp events",
				"script": "policy.sh"
			}
		}
	}`

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

	event := createTestEvent(t, testSigner, "Hello Nostr!", 1)

	t.Logf("=== Policy Configuration ===")
	t.Logf("Whitelist: %v", policy.Kind.Whitelist)
	t.Logf("Blacklist: %v", policy.Kind.Blacklist)
	t.Logf("rules: %v", policy.rules)
	t.Logf("Default policy: %s", policy.DefaultPolicy)
	t.Logf("")
	t.Logf("=== Event Details ===")
	t.Logf("Event kind: %d", event.Kind)
	t.Logf("")
	t.Logf("=== Policy Check Flow ===")

	// Step 1: Check kinds policy
	kindsAllowed := policy.checkKindsPolicy("write", event.Kind)
	t.Logf("1. checkKindsPolicy(access=write, kind=%d) returned: %v", event.Kind, kindsAllowed)

	// Full policy check
	allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
	t.Logf("2. CheckPolicy returned: allowed=%v, err=%v", allowed, err)

	if allowed {
		t.Errorf("BUG REPRODUCED: Kind 1 should be REJECTED but was ALLOWED")
	}
}

// TestBugFix_FailSafeWhenConfigMissing tests the fix for the security bug
// where missing config would allow all events
func TestBugFix_FailSafeWhenConfigMissing(t *testing.T) {
	testSigner, testPubkey := generateTestKeypair(t)

	t.Run("Missing config with enabled policy causes panic", func(t *testing.T) {
		// When policy is enabled but config file is missing, NewWithManager should panic
		// This is a FATAL configuration error that must be fixed before the relay can start

		defer func() {
			r := recover()
			if r == nil {
				t.Error("Expected panic when policy is enabled but config is missing, but no panic occurred")
			} else {
				// Verify the panic message mentions the config error
				panicMsg := fmt.Sprintf("%v", r)
				if !strings.Contains(panicMsg, "fatal policy configuration error") {
					t.Errorf("Panic message should mention 'fatal policy configuration error', got: %s", panicMsg)
				}
				t.Logf("Correctly panicked with message: %s", panicMsg)
			}
		}()

		// Simulate NewWithManager behavior by directly testing the panic path
		// Create a policy manager with a non-existent config path
		ctx := context.Background()
		tmpDir := t.TempDir()
		configDir := filepath.Join(tmpDir, "ORLY_TEST_NO_CONFIG")
		configPath := filepath.Join(configDir, "policy.json")

		// Ensure directory exists but file doesn't
		os.MkdirAll(configDir, 0755)

		manager := &PolicyManager{
			ctx:        ctx,
			configDir:  configDir,
			scriptPath: filepath.Join(configDir, "policy.sh"),
			enabled:    true,
			runners:    make(map[string]*ScriptRunner),
		}

		policy := &P{
			DefaultPolicy: "allow",
			manager:       manager,
		}

		// Try to load from nonexistent file - this should trigger the panic
		if err := policy.LoadFromFile(configPath); err != nil {
			// Simulate what NewWithManager does when LoadFromFile fails
			log.E.F(
				"FATAL: Policy system is ENABLED (ORLY_POLICY_ENABLED=true) but configuration failed to load from %s: %v",
				configPath, err,
			)
			log.E.F("The relay cannot start with an invalid policy configuration.")
			log.E.F("Fix: Either disable the policy system (ORLY_POLICY_ENABLED=false) or ensure %s exists and contains valid JSON", configPath)
			panic(fmt.Sprintf("fatal policy configuration error: %v", err))
		}

		// Should never reach here
		t.Error("Should have panicked but didn't")
	})

	t.Run("Empty whitelist respects default_policy=deny", func(t *testing.T) {
		// Create policy with empty whitelist and deny default
		policy := &P{
			DefaultPolicy: "deny",
			Kind: Kinds{
				Whitelist: []int{}, // Empty
			},
			rules: make(map[int]Rule), // No rules
		}

		event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
		allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if allowed {
			t.Error("Kind 1 should be REJECTED with empty whitelist and default_policy=deny")
		}
	})

	t.Run("Empty whitelist respects default_policy=allow", func(t *testing.T) {
		// Create policy with empty whitelist and allow default
		policy := &P{
			DefaultPolicy: "allow",
			Kind: Kinds{
				Whitelist: []int{}, // Empty
			},
			rules: make(map[int]Rule), // No rules
		}

		event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
		allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
		if err != nil {
			t.Fatalf("Unexpected error: %v", err)
		}
		if !allowed {
			t.Error("Kind 1 should be ALLOWED with empty whitelist and default_policy=allow")
		}
	})
}
