/**
 * Claude Code Plugin Security System
 *
 * Features:
 * - Permission validation
 * - File system sandboxing
 * - Command execution validation
 * - Network access control
 * - Resource limits
 * - Code injection prevention
 */

import fs from 'fs/promises'
import path from 'path'
import { createHash } from 'crypto'

// ============================================================================
// TYPES AND INTERFACES
// ============================================================================

export type Permission =
  | 'read:files'
  | 'write:files'
  | 'execute:commands'
  | 'network:request'
  | 'read:config'
  | 'write:config'
  | 'hook:events'
  | 'read:secrets'

export interface SecurityPolicy {
  allowedPaths: string[]
  deniedPaths: string[]
  allowedCommands: string[]
  deniedCommands: string[]
  allowedDomains: string[]
  deniedDomains: string[]
  maxFileSize: number
  maxExecutionTime: number
  requireCodeSigning: boolean
}

export interface SecurityContext {
  pluginName: string
  permissions: Permission[]
  workingDirectory: string
  startTime: number
}

export interface ValidationResult {
  allowed: boolean
  reason?: string
  modifiedValue?: any
}

// ============================================================================
// SECURITY MANAGER CLASS
// ============================================================================

export class SecurityManager {
  private policy: SecurityPolicy
  private contexts: Map<string, SecurityContext> = new Map()
  private auditLog: AuditLog[] = []

  constructor(policy?: Partial<SecurityPolicy>) {
    this.policy = {
      allowedPaths: [],
      deniedPaths: [],
      allowedCommands: [],
      deniedCommands: ['rm -rf /', 'format', 'mkfs'],
      allowedDomains: [],
      deniedDomains: [],
      maxFileSize: 100 * 1024 * 1024, // 100MB
      maxExecutionTime: 30000, // 30 seconds
      requireCodeSigning: false,
      ...policy,
    }
  }

  // ============================================================================
  // CONTEXT MANAGEMENT
  // ============================================================================

  createContext(pluginName: string, permissions: Permission[]): SecurityContext {
    const context: SecurityContext = {
      pluginName,
      permissions,
      workingDirectory: process.cwd(),
      startTime: Date.now(),
    }

    this.contexts.set(pluginName, context)
    return context
  }

  getContext(pluginName: string): SecurityContext | undefined {
    return this.contexts.get(pluginName)
  }

  removeContext(pluginName: string): void {
    this.contexts.delete(pluginName)
  }

  // ============================================================================
  // PERMISSION VALIDATION
  // ============================================================================

  hasPermission(pluginName: string, permission: Permission): boolean {
    const context = this.getContext(pluginName)
    if (!context) return false

    return context.permissions.includes(permission)
  }

  validatePermissions(
    pluginName: string,
    requiredPermissions: Permission[]
  ): ValidationResult {
    const context = this.getContext(pluginName)

    if (!context) {
      return {
        allowed: false,
        reason: 'Plugin context not found',
      }
    }

    const missing = requiredPermissions.filter(
      (perm) => !context.permissions.includes(perm)
    )

    if (missing.length > 0) {
      return {
        allowed: false,
        reason: `Missing permissions: ${missing.join(', ')}`,
      }
    }

    return { allowed: true }
  }

  // ============================================================================
  // FILE SYSTEM VALIDATION
  // ============================================================================

  async validateFileAccess(
    pluginName: string,
    filePath: string,
    mode: 'read' | 'write'
  ): Promise<ValidationResult> {
    const permission = mode === 'read' ? 'read:files' : 'write:files'

    if (!this.hasPermission(pluginName, permission)) {
      return {
        allowed: false,
        reason: `Missing permission: ${permission}`,
      }
    }

    const resolvedPath = path.resolve(filePath)

    // Check denied paths first
    for (const denied of this.policy.deniedPaths) {
      if (resolvedPath.startsWith(path.resolve(denied))) {
        return {
          allowed: false,
          reason: `Access denied to path: ${filePath}`,
        }
      }
    }

    // If allowed paths are specified, check against them
    if (this.policy.allowedPaths.length > 0) {
      const allowed = this.policy.allowedPaths.some((allowedPath) =>
        resolvedPath.startsWith(path.resolve(allowedPath))
      )

      if (!allowed) {
        return {
          allowed: false,
          reason: `Path not in allowed list: ${filePath}`,
        }
      }
    }

    // Check file size for writes
    if (mode === 'write') {
      try {
        const stats = await fs.stat(resolvedPath)
        if (stats.size > this.policy.maxFileSize) {
          return {
            allowed: false,
            reason: `File too large: ${stats.size} bytes`,
          }
        }
      } catch {
        // File doesn't exist yet, that's fine for writes
      }
    }

    return { allowed: true }
  }

  // ============================================================================
  // COMMAND VALIDATION
  // ============================================================================

  validateCommand(pluginName: string, command: string): ValidationResult {
    if (!this.hasPermission(pluginName, 'execute:commands')) {
      return {
        allowed: false,
        reason: 'Missing permission: execute:commands',
      }
    }

    // Check denied commands
    for (const denied of this.policy.deniedCommands) {
      if (command.includes(denied)) {
        return {
          allowed: false,
          reason: `Command contains denied pattern: ${denied}`,
        }
      }
    }

    // Check for dangerous patterns
    const dangerousPatterns = [
      /\brm\s+-rf\s+\//,
      /\bformat\s+[a-z]:/i,
      /\bdel\s+\/[sq]/i,
      /\>\s*\/dev\/[a-z]+/,
      /\|.*\brm\b/,
      /&&.*\brm\b/,
      /;.*\brm\b/,
    ]

    for (const pattern of dangerousPatterns) {
      if (pattern.test(command)) {
        return {
          allowed: false,
          reason: 'Command contains dangerous pattern',
        }
      }
    }

    return { allowed: true }
  }

  // ============================================================================
  // NETWORK VALIDATION
  // ============================================================================

  validateNetworkRequest(pluginName: string, url: string): ValidationResult {
    if (!this.hasPermission(pluginName, 'network:request')) {
      return {
        allowed: false,
        reason: 'Missing permission: network:request',
      }
    }

    try {
      const urlObj = new URL(url)
      const domain = urlObj.hostname

      // Check denied domains
      for (const denied of this.policy.deniedDomains) {
        if (domain === denied || domain.endsWith(`.${denied}`)) {
          return {
            allowed: false,
            reason: `Access denied to domain: ${domain}`,
          }
        }
      }

      // If allowed domains are specified, check against them
      if (this.policy.allowedDomains.length > 0) {
        const allowed = this.policy.allowedDomains.some(
          (allowed) => domain === allowed || domain.endsWith(`.${allowed}`)
        )

        if (!allowed) {
          return {
            allowed: false,
            reason: `Domain not in allowed list: ${domain}`,
          }
        }
      }
    } catch {
      return {
        allowed: false,
        reason: 'Invalid URL',
      }
    }

    return { allowed: true }
  }

  // ============================================================================
  // CODE INJECTION PREVENTION
  // ============================================================================

  sanitizeInput(input: string): string {
    // Remove potential code injection patterns
    return input
      .replace(/<script[^>]*>.*?<\/script>/gi, '')
      .replace(/javascript:/gi, '')
      .replace(/on\w+\s*=/gi, '')
      .replace(/\${.*?}/g, '') // Template literals
      .replace(/`.*?`/g, '') // Backtick expressions
      .replace(/eval\s*\(/gi, '')
      .replace(/Function\s*\(/gi, '')
      .replace(/setTimeout\s*\(/gi, '')
      .replace(/setInterval\s*\(/gi, '')
  }

  validateScriptCode(code: string): ValidationResult {
    const dangerousPatterns = [
      /eval\s*\(/,
      /Function\s*\(/,
      /require\s*\(\s*['"`]fs['"`]\s*\)/,
      /require\s*\(\s*['"`]child_process['"`]\s*\)/,
      /process\s*\.\s*exit/,
      /\.\.\//, // Path traversal
      /~\//, // Home directory access
    ]

    for (const pattern of dangerousPatterns) {
      if (pattern.test(code)) {
        return {
          allowed: false,
          reason: `Code contains dangerous pattern: ${pattern.source}`,
        }
      }
    }

    return { allowed: true }
  }

  // ============================================================================
  // INTEGRITY CHECKING
  // ============================================================================

  async calculateFileIntegrity(filePath: string): Promise<string> {
    const content = await fs.readFile(filePath)
    return createHash('sha256').update(content).digest('hex')
  }

  async verifyPluginIntegrity(
    pluginPath: string,
    expectedIntegrity: string
  ): Promise<boolean> {
    const actualIntegrity = await this.calculateDirectoryIntegrity(pluginPath)
    return actualIntegrity === expectedIntegrity
  }

  async calculateDirectoryIntegrity(dirPath: string): Promise<string> {
    const hash = createHash('sha256')
    const files = await this.getAllFiles(dirPath)

    for (const file of files.sort()) {
      const content = await fs.readFile(file)
      hash.update(content)
    }

    return hash.digest('hex')
  }

  private async getAllFiles(dirPath: string): Promise<string[]> {
    const files: string[] = []
    const entries = await fs.readdir(dirPath, { withFileTypes: true })

    for (const entry of entries) {
      const fullPath = path.join(dirPath, entry.name)
      if (entry.isDirectory()) {
        files.push(...(await this.getAllFiles(fullPath)))
      } else {
        files.push(fullPath)
      }
    }

    return files
  }

  // ============================================================================
  // AUDIT LOGGING
  // ============================================================================

  logAccess(
    pluginName: string,
    action: string,
    resource: string,
    allowed: boolean
  ): void {
    const entry: AuditLog = {
      timestamp: new Date().toISOString(),
      pluginName,
      action,
      resource,
      allowed,
    }

    this.auditLog.push(entry)

    // Keep only last 1000 entries
    if (this.auditLog.length > 1000) {
      this.auditLog = this.auditLog.slice(-1000)
    }
  }

  getAuditLog(pluginName?: string): AuditLog[] {
    if (pluginName) {
      return this.auditLog.filter((entry) => entry.pluginName === pluginName)
    }
    return [...this.auditLog]
  }

  clearAuditLog(): void {
    this.auditLog = []
  }
}

// ============================================================================
// TYPES
// ============================================================================

interface AuditLog {
  timestamp: string
  pluginName: string
  action: string
  resource: string
  allowed: boolean
}

// ============================================================================
// SANDBOX CLASS - Isolated Execution Environment
// ============================================================================

export class Sandbox {
  private security: SecurityManager
  private context: SecurityContext

  constructor(security: SecurityManager, context: SecurityContext) {
    this.security = security
    this.context = context
  }

  async readFile(filePath: string): Promise<string> {
    const validation = await this.security.validateFileAccess(
      this.context.pluginName,
      filePath,
      'read'
    )

    if (!validation.allowed) {
      this.security.logAccess(
        this.context.pluginName,
        'read:file',
        filePath,
        false
      )
      throw new Error(validation.reason)
    }

    this.security.logAccess(
      this.context.pluginName,
      'read:file',
      filePath,
      true
    )

    return await fs.readFile(filePath, 'utf-8')
  }

  async writeFile(filePath: string, content: string): Promise<void> {
    const validation = await this.security.validateFileAccess(
      this.context.pluginName,
      filePath,
      'write'
    )

    if (!validation.allowed) {
      this.security.logAccess(
        this.context.pluginName,
        'write:file',
        filePath,
        false
      )
      throw new Error(validation.reason)
    }

    this.security.logAccess(
      this.context.pluginName,
      'write:file',
      filePath,
      true
    )

    await fs.writeFile(filePath, content, 'utf-8')
  }

  executeCommand(command: string): Promise<string> {
    const validation = this.security.validateCommand(
      this.context.pluginName,
      command
    )

    if (!validation.allowed) {
      this.security.logAccess(
        this.context.pluginName,
        'execute:command',
        command,
        false
      )
      throw new Error(validation.reason)
    }

    this.security.logAccess(
      this.context.pluginName,
      'execute:command',
      command,
      true
    )

    // Command execution would be implemented here
    return Promise.resolve('')
  }
}

// ============================================================================
// EXPORTS
// ============================================================================

export default SecurityManager
