/**
 * Security Manager
 * Implements MCP Design Guide Section 5.2 principles for zero-trust architecture
 */

import { ErrorHandler } from './error-handler.js';

export interface SecurityContext {
  userId?: string;
  sessionId: string;
  permissions: string[];
  roleLevel: 'read' | 'write' | 'admin' | 'system';
  origin: string;
  timestamp: number;
  ipAddress?: string;
}

export interface ToolSecurityPolicy {
  toolName: string;
  requiredPermissions: string[];
  minimumRoleLevel: SecurityContext['roleLevel'];
  requiresHumanApproval: boolean;
  maxUsagePerHour: number;
  allowedOrigins: string[];
  logLevel: 'none' | 'basic' | 'detailed';
}

export interface SecurityEvent {
  type: 'access_granted' | 'access_denied' | 'suspicious_activity' | 'policy_violation';
  toolName: string;
  context: SecurityContext;
  timestamp: number;
  details: Record<string, any>;
  riskLevel: 'low' | 'medium' | 'high' | 'critical';
}

/**
 * Implements zero-trust security model for MCP tool access
 */
export class SecurityManager {
  private static instance: SecurityManager;
  private securityPolicies: Map<string, ToolSecurityPolicy> = new Map();
  private securityEvents: SecurityEvent[] = [];
  private usageTracker: Map<string, { count: number; lastReset: number }> = new Map();
  private pendingApprovals: Map<string, any> = new Map();

  private constructor() {
    this.initializeDefaultPolicies();
  }

  static getInstance(): SecurityManager {
    if (!SecurityManager.instance) {
      SecurityManager.instance = new SecurityManager();
    }
    return SecurityManager.instance;
  }

  /**
   * Initialize default security policies for critical tools
   */
  private initializeDefaultPolicies(): void {
    // High-risk development tools
    this.addSecurityPolicy({
      toolName: 'run_tests',
      requiredPermissions: ['execute:tests'],
      minimumRoleLevel: 'write',
      requiresHumanApproval: false,
      maxUsagePerHour: 50,
      allowedOrigins: ['*'],
      logLevel: 'basic'
    });

    // Code modification tools
    this.addSecurityPolicy({
      toolName: 'implement_feature_code',
      requiredPermissions: ['modify:code'],
      minimumRoleLevel: 'write',
      requiresHumanApproval: true,
      maxUsagePerHour: 20,
      allowedOrigins: ['*'],
      logLevel: 'detailed'
    });

    // Data access tools
    this.addSecurityPolicy({
      toolName: 'store_memory',
      requiredPermissions: ['write:memory'],
      minimumRoleLevel: 'write',
      requiresHumanApproval: false,
      maxUsagePerHour: 100,
      allowedOrigins: ['*'],
      logLevel: 'basic'
    });

    // Security scanning tools
    this.addSecurityPolicy({
      toolName: 'perform_security_scan',
      requiredPermissions: ['execute:security'],
      minimumRoleLevel: 'read',
      requiresHumanApproval: false,
      maxUsagePerHour: 10,
      allowedOrigins: ['*'],
      logLevel: 'detailed'
    });

    // Administrative tools
    this.addSecurityPolicy({
      toolName: 'configure_security_settings',
      requiredPermissions: ['admin:security'],
      minimumRoleLevel: 'admin',
      requiresHumanApproval: true,
      maxUsagePerHour: 5,
      allowedOrigins: ['*'],
      logLevel: 'detailed'
    });

    // File system access
    this.addSecurityPolicy({
      toolName: 'create_kanban_board',
      requiredPermissions: ['write:kanban'],
      minimumRoleLevel: 'write',
      requiresHumanApproval: false,
      maxUsagePerHour: 20,
      allowedOrigins: ['*'],
      logLevel: 'basic'
    });
  }

  /**
   * Add or update a security policy for a tool
   */
  addSecurityPolicy(policy: ToolSecurityPolicy): void {
    this.securityPolicies.set(policy.toolName, policy);
  }

  /**
   * Validate access to a tool based on security context and policies
   */
  async validateToolAccess(
    toolName: string,
    context: SecurityContext,
    parameters?: any
  ): Promise<{ allowed: boolean; reason?: string; requiresApproval?: boolean }> {
    const policy = this.securityPolicies.get(toolName);
    
    // If no policy exists, apply default restrictions
    if (!policy) {
      this.logSecurityEvent({
        type: 'policy_violation',
        toolName,
        context,
        timestamp: Date.now(),
        details: { reason: 'No security policy defined' },
        riskLevel: 'medium'
      });
      
      // Default to allowing basic read operations
      if (this.isReadOnlyTool(toolName)) {
        return { allowed: true };
      }
      
      return { 
        allowed: false, 
        reason: 'No security policy defined for this tool' 
      };
    }

    // Check role level
    if (!this.hasRequiredRoleLevel(context.roleLevel, policy.minimumRoleLevel)) {
      this.logSecurityEvent({
        type: 'access_denied',
        toolName,
        context,
        timestamp: Date.now(),
        details: { 
          reason: 'Insufficient role level',
          required: policy.minimumRoleLevel,
          provided: context.roleLevel
        },
        riskLevel: 'medium'
      });
      
      return { 
        allowed: false, 
        reason: `Requires ${policy.minimumRoleLevel} role level or higher` 
      };
    }

    // Check permissions
    const hasPermissions = policy.requiredPermissions.every(permission => 
      context.permissions.includes(permission)
    );
    
    if (!hasPermissions) {
      this.logSecurityEvent({
        type: 'access_denied',
        toolName,
        context,
        timestamp: Date.now(),
        details: { 
          reason: 'Missing required permissions',
          required: policy.requiredPermissions,
          provided: context.permissions
        },
        riskLevel: 'high'
      });
      
      return { 
        allowed: false, 
        reason: `Missing required permissions: ${policy.requiredPermissions.join(', ')}` 
      };
    }

    // Check rate limiting
    if (!this.checkRateLimit(toolName, policy.maxUsagePerHour)) {
      this.logSecurityEvent({
        type: 'policy_violation',
        toolName,
        context,
        timestamp: Date.now(),
        details: { 
          reason: 'Rate limit exceeded',
          limit: policy.maxUsagePerHour
        },
        riskLevel: 'medium'
      });
      
      return { 
        allowed: false, 
        reason: `Rate limit exceeded: maximum ${policy.maxUsagePerHour} uses per hour` 
      };
    }

    // Check for suspicious patterns
    const suspiciousActivity = this.detectSuspiciousActivity(toolName, context, parameters);
    if (suspiciousActivity) {
      this.logSecurityEvent({
        type: 'suspicious_activity',
        toolName,
        context,
        timestamp: Date.now(),
        details: suspiciousActivity,
        riskLevel: 'high'
      });
      
      return { 
        allowed: false, 
        reason: 'Suspicious activity detected' 
      };
    }

    // Check if human approval is required
    if (policy.requiresHumanApproval) {
      return {
        allowed: false,
        requiresApproval: true,
        reason: 'This operation requires human approval'
      };
    }

    // Log successful access
    this.logSecurityEvent({
      type: 'access_granted',
      toolName,
      context,
      timestamp: Date.now(),
      details: { parameters: this.sanitizeParameters(parameters) },
      riskLevel: 'low'
    });

    // Update usage counter
    this.updateUsageCounter(toolName);

    return { allowed: true };
  }

  /**
   * Request human approval for a tool operation
   */
  async requestHumanApproval(
    toolName: string,
    context: SecurityContext,
    parameters: any,
    justification: string
  ): Promise<string> {
    const approvalId = `approval_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
    
    this.pendingApprovals.set(approvalId, {
      toolName,
      context,
      parameters: this.sanitizeParameters(parameters),
      justification,
      timestamp: Date.now(),
      status: 'pending'
    });

    this.logSecurityEvent({
      type: 'access_denied',
      toolName,
      context,
      timestamp: Date.now(),
      details: { 
        reason: 'Pending human approval',
        approvalId,
        justification
      },
      riskLevel: 'medium'
    });

    return approvalId;
  }



  /**
   * Generate security metrics and alerts
   */
  generateSecurityMetrics(): {
    totalEvents: number;
    accessDenied: number;
    suspiciousActivity: number;
    highRiskEvents: number;
    topTargetedTools: Array<{ tool: string; count: number }>;
    alerts: string[];
  } {
    const total = this.securityEvents.length;
    const denied = this.securityEvents.filter(e => e.type === 'access_denied').length;
    const suspicious = this.securityEvents.filter(e => e.type === 'suspicious_activity').length;
    const highRisk = this.securityEvents.filter(e => e.riskLevel === 'high' || e.riskLevel === 'critical').length;

    // Count tool usage
    const toolCounts: Record<string, number> = {};
    this.securityEvents.forEach(event => {
      toolCounts[event.toolName] = (toolCounts[event.toolName] || 0) + 1;
    });

    const topTools = Object.entries(toolCounts)
      .sort(([,a], [,b]) => b - a)
      .slice(0, 5)
      .map(([tool, count]) => ({ tool, count }));

    // Generate alerts
    const alerts: string[] = [];
    if (suspicious > 0) {
      alerts.push(`${suspicious} suspicious activities detected`);
    }
    if (highRisk > total * 0.1) {
      alerts.push(`High percentage of high-risk events (${Math.round(highRisk / total * 100)}%)`);
    }
    if (denied > total * 0.3) {
      alerts.push(`High access denial rate (${Math.round(denied / total * 100)}%)`);
    }

    return {
      totalEvents: total,
      accessDenied: denied,
      suspiciousActivity: suspicious,
      highRiskEvents: highRisk,
      topTargetedTools: topTools,
      alerts
    };
  }

  private hasRequiredRoleLevel(userRole: SecurityContext['roleLevel'], requiredRole: SecurityContext['roleLevel']): boolean {
    const roleHierarchy = ['read', 'write', 'admin', 'system'];
    const userLevel = roleHierarchy.indexOf(userRole);
    const requiredLevel = roleHierarchy.indexOf(requiredRole);
    return userLevel >= requiredLevel;
  }

  private checkRateLimit(toolName: string, maxPerHour: number): boolean {
    const key = toolName;
    const now = Date.now();
    const hourInMs = 60 * 60 * 1000;
    
    let usage = this.usageTracker.get(key);
    if (!usage || now - usage.lastReset > hourInMs) {
      usage = { count: 0, lastReset: now };
      this.usageTracker.set(key, usage);
    }

    return usage.count < maxPerHour;
  }

  private updateUsageCounter(toolName: string): void {
    const key = toolName;
    const usage = this.usageTracker.get(key);
    if (usage) {
      usage.count++;
    }
  }

  private detectSuspiciousActivity(
    toolName: string,
    context: SecurityContext,
    parameters: any
  ): Record<string, any> | null {
    const suspiciousPatterns: Record<string, any> = {};

    // Check for rapid successive calls
    const recentEvents = this.securityEvents
      .filter(e => e.toolName === toolName && e.context.sessionId === context.sessionId)
      .filter(e => Date.now() - e.timestamp < 60000); // Last minute

    if (recentEvents.length > 10) {
      suspiciousPatterns.rapidCalls = {
        count: recentEvents.length,
        timeframe: '1 minute'
      };
    }

    // Check for unusual parameter patterns
    if (parameters) {
      // Look for potential injection attempts
      const paramString = JSON.stringify(parameters).toLowerCase();
      const injectionPatterns = [
        'script', 'eval(', 'exec(', 'system(', 'rm -rf', 
        'drop table', 'union select', '<script', 'javascript:'
      ];

      const foundPatterns = injectionPatterns.filter(pattern => 
        paramString.includes(pattern)
      );

      if (foundPatterns.length > 0) {
        suspiciousPatterns.injectionAttempt = {
          patterns: foundPatterns
        };
      }
    }

    return Object.keys(suspiciousPatterns).length > 0 ? suspiciousPatterns : null;
  }

  private isReadOnlyTool(toolName: string): boolean {
    const readOnlyTools = [
      'list_kanban_boards',
      'list_agile_sprints',
      'list_agile_backlog',
      'get_sprint_status',
      'search_memories',
      'check_project_status',
      'generate_velocity_report'
    ];
    return readOnlyTools.includes(toolName);
  }

  private sanitizeParameters(parameters: any): any {
    if (!parameters) return parameters;

    const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth'];
    const sanitized = JSON.parse(JSON.stringify(parameters));

    const sanitizeObject = (obj: any): void => {
      if (typeof obj !== 'object' || obj === null) return;

      Object.keys(obj).forEach(key => {
        if (sensitiveKeys.some(sensitive => key.toLowerCase().includes(sensitive))) {
          obj[key] = '[REDACTED]';
        } else if (typeof obj[key] === 'object') {
          sanitizeObject(obj[key]);
        }
      });
    };

    sanitizeObject(sanitized);
    return sanitized;
  }

  private logSecurityEvent(event: SecurityEvent): void {
    this.securityEvents.push(event);

    // Keep only last 1000 events to prevent memory issues
    if (this.securityEvents.length > 1000) {
      this.securityEvents = this.securityEvents.slice(-1000);
    }

    // Log to console for immediate visibility
    const logLevel = event.riskLevel === 'high' || event.riskLevel === 'critical' ? 'error' : 'info';
    console[logLevel](`[Security] ${event.type}: ${event.toolName}`, {
      user: event.context.userId,
      session: event.context.sessionId,
      risk: event.riskLevel,
      details: event.details
    });
  }

  /**
   * Get comprehensive security status overview
   */
  async getSecurityStatus(): Promise<any> {
    const now = Date.now();
    const last24Hours = now - (24 * 60 * 60 * 1000);
    
    const recentEvents = this.securityEvents.filter(e => e.timestamp >= last24Hours);
    const denials = recentEvents.filter(e => e.type === 'access_denied').length;
    const violations = recentEvents.filter(e => e.type === 'policy_violation').length;
    const approvals = recentEvents.filter(e => e.type === 'access_granted').length;
    
    const alerts = [];
    if (denials > 10) {
      alerts.push({
        severity: 'high',
        message: `High number of access denials: ${denials} in last 24 hours`
      });
    }
    if (violations > 5) {
      alerts.push({
        severity: 'critical',
        message: `Multiple policy violations detected: ${violations}`
      });
    }
    
    return {
      overall: {
        status: alerts.length === 0 ? 'healthy' : 'attention_required',
        level: 'high',
        lastScan: new Date().toISOString(),
        pendingApprovals: this.pendingApprovals.size
      },
      recentEvents: {
        total: recentEvents.length,
        denials,
        violations,
        approvals
      },
      policies: {
        active: this.securityPolicies.size,
        accessControl: true,
        humanApproval: true,
        auditLogging: true,
        riskAssessment: true
      },
      alerts,
      recommendations: [
        'Review access denial patterns',
        'Update security policies regularly',
        'Monitor high-risk operations'
      ]
    };
  }

  /**
   * Get security events with filtering
   */
  getSecurityEvents(filters: {
    timeRange?: string;
    eventTypes?: string[];
    severity?: string;
  }): SecurityEvent[] {
    let events = [...this.securityEvents];
    
    // Apply time range filter
    if (filters.timeRange) {
      const ranges: Record<string, number> = {
        '1h': 60 * 60 * 1000,
        '24h': 24 * 60 * 60 * 1000,
        '7d': 7 * 24 * 60 * 60 * 1000,
        '30d': 30 * 24 * 60 * 60 * 1000
      };
      const cutoff = Date.now() - (ranges[filters.timeRange] || ranges['24h']);
      events = events.filter(e => e.timestamp >= cutoff);
    }
    
    // Apply event type filter
    if (filters.eventTypes && filters.eventTypes.length > 0) {
      events = events.filter(e => filters.eventTypes!.includes(e.type));
    }
    
    // Apply severity filter
    if (filters.severity) {
      const severityLevels = ['low', 'medium', 'high', 'critical'];
      const minLevel = severityLevels.indexOf(filters.severity);
      events = events.filter(e => severityLevels.indexOf(e.riskLevel) >= minLevel);
    }
    
    // Sort by timestamp descending
    return events.sort((a, b) => b.timestamp - a.timestamp);
  }

  /**
   * Configure security policy settings
   */
  async configureSecurityPolicy(config: {
    requireApprovalFor?: string[];
    roles?: Record<string, string[]>;
    riskThresholds?: Record<string, number>;
    logLevel?: string;
  }): Promise<void> {
    // Update approval requirements
    if (config.requireApprovalFor) {
      config.requireApprovalFor.forEach(toolName => {
        const policy = this.securityPolicies.get(toolName);
        if (policy) {
          policy.requiresHumanApproval = true;
        }
      });
    }
    
    // Update role definitions (simplified for now)
    if (config.roles) {
      console.log('Role definitions updated:', config.roles);
    }
    
    // Update risk thresholds
    if (config.riskThresholds) {
      console.log('Risk thresholds updated:', config.riskThresholds);
    }
    
    // Update log level
    if (config.logLevel) {
      console.log('Security log level set to:', config.logLevel);
    }
    
    // Log configuration change
    this.logSecurityEvent({
      type: 'policy_violation',
      toolName: 'configure_security_policy',
      context: createSecurityContext(
        'system',
        'config-update',
        ['admin'],
        'admin',
        'internal'
      ),
      timestamp: Date.now(),
      details: { config },
      riskLevel: 'low'
    });
  }

  /**
   * Process approval request
   */
  processApproval(params: {
    approvalId: string;
    decision: 'approve' | 'deny';
    reason?: string;
  }): any {
    const approval = this.pendingApprovals.get(params.approvalId);
    
    if (!approval) {
      throw new Error(`Approval request ${params.approvalId} not found`);
    }
    
    this.pendingApprovals.delete(params.approvalId);
    
    const result = {
      approvalId: params.approvalId,
      operation: approval.toolName,
      decision: params.decision,
      reason: params.reason,
      executionResult: params.decision === 'approve' ? {
        success: true,
        message: 'Operation approved and executed'
      } : null
    };
    
    // Log the approval decision
    this.logSecurityEvent({
      type: params.decision === 'approve' ? 'access_granted' : 'access_denied',
      toolName: approval.toolName,
      context: approval.context,
      timestamp: Date.now(),
      details: {
        approvalId: params.approvalId,
        decision: params.decision,
        reason: params.reason
      },
      riskLevel: 'medium'
    });
    
    return result;
  }

  /**
   * Get pending approval requests (optional method for dashboard)
   */
  getPendingApprovals?(options?: { status?: string; toolName?: string }): Promise<any[]> {
    // This method is optional and can be implemented by extending classes
    // For now, return a mock implementation
    const mockApprovals = Array.from(this.pendingApprovals.entries()).map(([id, approval]) => ({
      id,
      toolName: approval.toolName,
      requestedBy: approval.context.userId || 'unknown',
      status: approval.status,
      createdAt: new Date(approval.timestamp).toISOString(),
      context: approval.parameters
    }));

    // Apply filters if provided
    let filtered = mockApprovals;
    if (options?.status && options.status !== 'all') {
      filtered = filtered.filter(a => a.status === options.status);
    }
    if (options?.toolName) {
      filtered = filtered.filter(a => a.toolName === options.toolName);
    }

    return Promise.resolve(filtered);
  }
}

/**
 * Create security context from request information
 */
export function createSecurityContext(
  userId: string | undefined,
  sessionId: string,
  permissions: string[],
  roleLevel: SecurityContext['roleLevel'],
  origin: string = 'unknown',
  ipAddress?: string
): SecurityContext {
  return {
    userId,
    sessionId,
    permissions,
    roleLevel,
    origin,
    timestamp: Date.now(),
    ipAddress
  };
}

/**
 * Decorator for automatic security validation
 */
export function requiresSecurity(
  requiredPermissions: string[],
  minimumRoleLevel: SecurityContext['roleLevel'] = 'read'
) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const securityManager = SecurityManager.getInstance();
      
      // Extract security context from arguments (assume first arg has security info)
      const context = args[0]?.securityContext;
      if (!context) {
        throw ErrorHandler.createValidationError(
          'securityContext',
          undefined,
          'is required for security validation',
          { tool: propertyKey, module: target.constructor.name }
        );
      }

      const validation = await securityManager.validateToolAccess(
        propertyKey,
        context,
        args[0]
      );

      if (!validation.allowed) {
        if (validation.requiresApproval) {
          const approvalId = await securityManager.requestHumanApproval(
            propertyKey,
            context,
            args[0],
            `${target.constructor.name}.${propertyKey} requires approval`
          );
          
          throw ErrorHandler.transformError(
            new Error(`Human approval required. Approval ID: ${approvalId}`),
            { tool: propertyKey, module: target.constructor.name }
          );
        }

        throw ErrorHandler.transformError(
          new Error(`Access denied: ${validation.reason}`),
          { tool: propertyKey, module: target.constructor.name }
        );
      }

      return await originalMethod.apply(this, args);
    };

    return descriptor;
  };
}