package crypto

import (
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"testing"
	"time"

	"chorus.services/bzzz/pkg/config"
)

// TestSecurityConfig tests SecurityConfig enforcement
func TestSecurityConfig(t *testing.T) {
	// Create temporary audit log file
	tmpDir, err := ioutil.TempDir("", "bzzz_security_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	// Test cases for security configuration
	testCases := []struct {
		name                string
		keyRotationDays     int
		auditLogging        bool
		expectWarnings      int
		expectRotationJobs  bool
	}{
		{
			name:               "audit_logging_disabled",
			keyRotationDays:    90,
			auditLogging:       false,
			expectWarnings:     1, // Warning for disabled audit logging
			expectRotationJobs: true,
		},
		{
			name:               "key_rotation_disabled", 
			keyRotationDays:    0,
			auditLogging:       true,
			expectWarnings:     1, // Warning for disabled key rotation
			expectRotationJobs: false,
		},
		{
			name:               "security_fully_enabled",
			keyRotationDays:    30,
			auditLogging:       true,
			expectWarnings:     0,
			expectRotationJobs: true,
		},
		{
			name:               "both_security_features_disabled",
			keyRotationDays:    0,
			auditLogging:       false,
			expectWarnings:     2, // Warnings for both disabled features
			expectRotationJobs: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create test configuration
			cfg := &config.Config{
				Agent: config.AgentConfig{
					ID: "test-agent",
				},
				Security: config.SecurityConfig{
					KeyRotationDays: tc.keyRotationDays,
					AuditLogging:    tc.auditLogging,
					AuditPath:       fmt.Sprintf("%s/audit-%s.log", tmpDir, tc.name),
				},
			}

			// Create mock audit logger
			mockLogger := &MockAuditLogger{events: make([]*SecurityEvent, 0)}
			
			// Create mock key store
			mockKeyStore := &MockKeyStore{
				keys: make(map[string]*SecureKeyData),
			}

			// Create key manager
			km, err := NewKeyManager(cfg, mockKeyStore, mockLogger)
			if err != nil {
				t.Fatalf("Failed to create key manager: %v", err)
			}
			defer func() {
				if km.rotationScheduler.running {
					km.rotationScheduler.Stop()
				}
			}()

			// Give the key manager time to initialize
			time.Sleep(100 * time.Millisecond)

			// Check audit logger for expected warnings
			securityWarnings := 0
			for _, event := range mockLogger.events {
				if event.EventType == "security_warning" {
					securityWarnings++
				}
			}

			if securityWarnings != tc.expectWarnings {
				t.Errorf("Expected %d security warnings, got %d", tc.expectWarnings, securityWarnings)
			}

			// Check if rotation scheduler is running
			isRunning := km.rotationScheduler.running
			if tc.expectRotationJobs && !isRunning {
				t.Errorf("Expected rotation scheduler to be running")
			} else if !tc.expectRotationJobs && isRunning {
				t.Errorf("Expected rotation scheduler to not be running")
			}

			// Test key rotation monitoring
			if tc.keyRotationDays > 0 {
				testKeyRotationMonitoring(t, km, mockKeyStore, mockLogger)
			}
		})
	}
}

// testKeyRotationMonitoring tests the key rotation monitoring functionality
func testKeyRotationMonitoring(t *testing.T, km *KeyManager, keyStore *MockKeyStore, mockLogger *MockAuditLogger) {
	// Create an old key that should trigger rotation warning
	oldKey := &SecureKeyData{
		KeyID:     "old-test-key",
		KeyType:   "age-x25519",
		CreatedAt: time.Now().Add(-100 * 24 * time.Hour), // 100 days old
		Status:    KeyStatusActive,
	}
	keyStore.keys[oldKey.KeyID] = oldKey

	// Create metadata for the old key
	oldKeyMeta := &KeyMetadata{
		KeyID:     "old-test-key",
		KeyType:   "age-x25519",
		RoleID:    "test-role",
		CreatedAt: time.Now().Add(-100 * 24 * time.Hour),
		Status:    KeyStatusActive,
	}
	keyStore.metadata = append(keyStore.metadata, oldKeyMeta)

	// Run key rotation check
	km.checkKeysForRotation()

	// Give time for async operations
	time.Sleep(100 * time.Millisecond)

	// Check if rotation warning was logged
	rotationWarnings := 0
	for _, event := range mockLogger.keyRotationEvents {
		if event.Reason == "key_rotation_overdue" {
			rotationWarnings++
		}
	}

	if rotationWarnings == 0 {
		t.Errorf("Expected at least one key rotation warning for overdue key")
	}
}

// TestDHTSecurityIntegration tests DHT security integration
func TestDHTSecurityIntegration(t *testing.T) {
	// Create test configuration
	cfg := &config.Config{
		Agent: config.AgentConfig{
			ID:   "test-agent",
			Role: "backend_developer",
		},
		Security: config.SecurityConfig{
			KeyRotationDays: 90,
			AuditLogging:    true,
			AuditPath:       "/tmp/test-audit.log",
		},
	}

	// Create mock DHT storage (simplified for testing)
	ctx := context.Background()
	
	// Test role-based access policies
	testCases := []struct {
		name         string
		currentRole  string
		operation    string
		shouldAllow  bool
		expectedError string
	}{
		{
			name:        "admin_can_store",
			currentRole: "admin",
			operation:   "store",
			shouldAllow: true,
		},
		{
			name:        "backend_developer_can_store",
			currentRole: "backend_developer", 
			operation:   "store",
			shouldAllow: true,
		},
		{
			name:          "readonly_cannot_store",
			currentRole:   "readonly_user",
			operation:     "store",
			shouldAllow:   false,
			expectedError: "read-only authority",
		},
		{
			name:        "all_roles_can_retrieve",
			currentRole: "qa_engineer",
			operation:   "retrieve",
			shouldAllow: true,
		},
		{
			name:          "suggestion_role_cannot_announce",
			currentRole:   "suggestion_role",
			operation:     "announce",
			shouldAllow:   false,
			expectedError: "lacks authority",
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Set role in config
			cfg.Agent.Role = tc.currentRole

			// Test the specific access policy check
			var err error
			switch tc.operation {
			case "store":
				err = checkStoreAccessPolicyTest(tc.currentRole)
			case "retrieve":
				err = checkRetrieveAccessPolicyTest(tc.currentRole)
			case "announce":
				err = checkAnnounceAccessPolicyTest(tc.currentRole)
			}

			if tc.shouldAllow {
				if err != nil {
					t.Errorf("Expected operation to be allowed but got error: %v", err)
				}
			} else {
				if err == nil {
					t.Errorf("Expected operation to be denied but it was allowed")
				} else if tc.expectedError != "" && err.Error() != tc.expectedError {
					// Check if error message contains expected substring
					if len(tc.expectedError) > 0 && !containsSubstring(err.Error(), tc.expectedError) {
						t.Errorf("Expected error to contain '%s', got '%s'", tc.expectedError, err.Error())
					}
				}
			}
		})
	}
}

// TestAuditLogging tests comprehensive audit logging
func TestAuditLogging(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "bzzz_audit_test")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	// Test audit logging for different operations
	testOperations := []struct {
		operation    string
		ucxlAddress  string
		role         string
		success      bool
		errorMsg     string
	}{
		{"store", "agent1:backend_developer:project1:task1", "backend_developer", true, ""},
		{"store", "agent2:invalid_role:project2:task2", "invalid_role", false, "unknown role"},
		{"retrieve", "agent1:backend_developer:project1:task1", "frontend_developer", true, ""},
		{"announce", "agent1:backend_developer:project1:task1", "senior_software_architect", true, ""},
		{"announce", "agent2:readonly:project2:task2", "readonly_user", false, "lacks authority"},
	}

	for _, op := range testOperations {
		t.Run(fmt.Sprintf("%s_%s_%v", op.operation, op.role, op.success), func(t *testing.T) {
			// Create configuration with audit logging enabled
			cfg := &config.Config{
				Agent: config.AgentConfig{
					ID:   "test-agent",
					Role: op.role,
				},
				Security: config.SecurityConfig{
					KeyRotationDays: 90,
					AuditLogging:    true,
					AuditPath:       fmt.Sprintf("%s/audit-%s.log", tmpDir, op.operation),
				},
			}

			// Simulate audit logging for the operation
			auditResult := simulateAuditOperation(cfg, op.operation, op.ucxlAddress, op.role, op.success, op.errorMsg)
			
			// Validate audit log entry
			if auditResult == nil {
				t.Errorf("Expected audit log entry but got nil")
				return
			}

			if auditResult["operation"] != op.operation {
				t.Errorf("Expected operation '%s', got '%s'", op.operation, auditResult["operation"])
			}

			if auditResult["role"] != op.role {
				t.Errorf("Expected role '%s', got '%s'", op.role, auditResult["role"])
			}

			if auditResult["success"] != op.success {
				t.Errorf("Expected success %v, got %v", op.success, auditResult["success"])
			}

			// Check for audit trail
			if auditTrail, ok := auditResult["audit_trail"].(string); !ok || auditTrail == "" {
				t.Errorf("Expected non-empty audit trail")
			}
		})
	}
}

// TestKeyRotationScheduling tests key rotation scheduling
func TestKeyRotationScheduling(t *testing.T) {
	cfg := &config.Config{
		Agent: config.AgentConfig{
			ID: "test-agent",
		},
		Security: config.SecurityConfig{
			KeyRotationDays: 7, // Short rotation for testing
			AuditLogging:    true,
			AuditPath:       "/tmp/test-rotation-audit.log",
		},
	}

	mockLogger := &MockAuditLogger{events: make([]*SecurityEvent, 0)}
	mockKeyStore := &MockKeyStore{keys: make(map[string]*SecureKeyData)}

	km, err := NewKeyManager(cfg, mockKeyStore, mockLogger)
	if err != nil {
		t.Fatalf("Failed to create key manager: %v", err)
	}
	defer func() {
		if km.rotationScheduler.running {
			km.rotationScheduler.Stop()
		}
	}()

	// Test that rotation jobs are scheduled for all roles
	roles := config.GetPredefinedRoles()
	expectedJobs := len(roles)

	if len(km.rotationScheduler.scheduledJobs) != expectedJobs {
		t.Errorf("Expected %d rotation jobs, got %d", expectedJobs, len(km.rotationScheduler.scheduledJobs))
	}

	// Test rotation policy is correctly set
	for _, job := range km.rotationScheduler.scheduledJobs {
		if job.Policy.RotationInterval != 7*24*time.Hour {
			t.Errorf("Expected rotation interval of 7 days, got %v", job.Policy.RotationInterval)
		}
		if !job.Policy.AutoRotate {
			t.Errorf("Expected auto-rotate to be enabled")
		}
	}
}

// Mock implementations for testing

type MockAuditLogger struct {
	events             []*SecurityEvent
	keyRotationEvents  []*KeyRotationEvent
}

func (m *MockAuditLogger) LogAccess(entry *AccessLogEntry) error {
	// Implementation for testing
	return nil
}

func (m *MockAuditLogger) LogKeyRotation(event *KeyRotationEvent) error {
	m.keyRotationEvents = append(m.keyRotationEvents, event)
	return nil
}

func (m *MockAuditLogger) LogSecurityEvent(event *SecurityEvent) error {
	m.events = append(m.events, event)
	return nil
}

func (m *MockAuditLogger) GetAuditTrail(criteria *AuditCriteria) ([]*AuditEvent, error) {
	return []*AuditEvent{}, nil
}

type MockKeyStore struct {
	keys     map[string]*SecureKeyData
	metadata []*KeyMetadata
}

func (m *MockKeyStore) StoreKey(keyID string, keyData *SecureKeyData) error {
	m.keys[keyID] = keyData
	return nil
}

func (m *MockKeyStore) RetrieveKey(keyID string) (*SecureKeyData, error) {
	if key, exists := m.keys[keyID]; exists {
		return key, nil
	}
	return nil, fmt.Errorf("key not found: %s", keyID)
}

func (m *MockKeyStore) DeleteKey(keyID string) error {
	delete(m.keys, keyID)
	return nil
}

func (m *MockKeyStore) ListKeys(filter *KeyFilter) ([]*KeyMetadata, error) {
	return m.metadata, nil
}

func (m *MockKeyStore) BackupKeys(criteria *BackupCriteria) (*KeyBackup, error) {
	return &KeyBackup{}, nil
}

func (m *MockKeyStore) RestoreKeys(backup *KeyBackup) error {
	return nil
}

// Test helper functions

func checkStoreAccessPolicyTest(role string) error {
	roles := config.GetPredefinedRoles()
	if _, exists := roles[role]; !exists {
		return fmt.Errorf("unknown creator role: %s", role)
	}
	
	roleData := roles[role]
	if roleData.AuthorityLevel == config.AuthorityReadOnly {
		return fmt.Errorf("role %s has read-only authority and cannot store content", role)
	}
	
	return nil
}

func checkRetrieveAccessPolicyTest(role string) error {
	roles := config.GetPredefinedRoles()
	if _, exists := roles[role]; !exists {
		return fmt.Errorf("unknown current role: %s", role)
	}
	
	return nil
}

func checkAnnounceAccessPolicyTest(role string) error {
	roles := config.GetPredefinedRoles()
	if _, exists := roles[role]; !exists {
		return fmt.Errorf("unknown current role: %s", role)
	}
	
	roleData := roles[role]
	if roleData.AuthorityLevel == config.AuthorityReadOnly || roleData.AuthorityLevel == config.AuthoritySuggestion {
		return fmt.Errorf("role %s lacks authority to announce content", role)
	}
	
	return nil
}

func simulateAuditOperation(cfg *config.Config, operation, ucxlAddress, role string, success bool, errorMsg string) map[string]interface{} {
	if !cfg.Security.AuditLogging || cfg.Security.AuditPath == "" {
		return nil
	}
	
	auditEntry := map[string]interface{}{
		"timestamp":     time.Now(),
		"operation":     operation,
		"node_id":       "test-node",
		"ucxl_address":  ucxlAddress,
		"role":          role,
		"success":       success,
		"error_message": errorMsg,
		"audit_trail":   fmt.Sprintf("DHT-%s-%s-%d", operation, ucxlAddress, time.Now().Unix()),
	}
	
	return auditEntry
}

func containsSubstring(str, substr string) bool {
	return len(substr) > 0 && len(str) >= len(substr) && 
		   func() bool {
			   for i := 0; i <= len(str)-len(substr); i++ {
				   if str[i:i+len(substr)] == substr {
					   return true
				   }
			   }
			   return false
		   }()
}

// Benchmarks for security operations

func BenchmarkSecurityPolicyCheck(b *testing.B) {
	roles := []string{"admin", "backend_developer", "frontend_developer", "security_expert"}
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		role := roles[i%len(roles)]
		checkStoreAccessPolicyTest(role)
	}
}

func BenchmarkAuditLogging(b *testing.B) {
	cfg := &config.Config{
		Agent: config.AgentConfig{ID: "bench-agent", Role: "backend_developer"},
		Security: config.SecurityConfig{AuditLogging: true, AuditPath: "/tmp/bench-audit.log"},
	}
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		simulateAuditOperation(cfg, "store", "test:address:bench:task", "backend_developer", true, "")
	}
}

func BenchmarkKeyRotationCheck(b *testing.B) {
	cfg := &config.Config{
		Agent: config.AgentConfig{ID: "bench-agent"},
		Security: config.SecurityConfig{KeyRotationDays: 90, AuditLogging: true},
	}
	
	mockLogger := &MockAuditLogger{events: make([]*SecurityEvent, 0)}
	mockKeyStore := &MockKeyStore{
		keys:     make(map[string]*SecureKeyData),
		metadata: []*KeyMetadata{},
	}
	
	// Add some test keys
	for i := 0; i < 10; i++ {
		keyMeta := &KeyMetadata{
			KeyID:     fmt.Sprintf("bench-key-%d", i),
			KeyType:   "age-x25519",
			RoleID:    "backend_developer",
			CreatedAt: time.Now().Add(-time.Duration(i*10) * 24 * time.Hour),
			Status:    KeyStatusActive,
		}
		mockKeyStore.metadata = append(mockKeyStore.metadata, keyMeta)
	}
	
	km, err := NewKeyManager(cfg, mockKeyStore, mockLogger)
	if err != nil {
		b.Fatalf("Failed to create key manager: %v", err)
	}
	defer func() {
		if km.rotationScheduler.running {
			km.rotationScheduler.Stop()
		}
	}()
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		km.checkKeysForRotation()
	}
}