/**
 * PermissionEngine - layered permission evaluation
 *
 * Implements contracts §13; DD §9.2.
 * Layered order 1–6: capability → profile → task scope → risk → credential override → user prompt.
 * INV-3: the mandatory gate for all side effects.
 *
 * @module packages/runtime/src/security/PermissionEngine
 */

import type { AgentType, AgentRuntimeContext, ToolDefinition, ToolCall } from '@aircoding/contracts'

import { PathClassifier, createPathClassifier } from './PathClassifier.js'
import { CommandRiskAnalyzer, createCommandRiskAnalyzer } from './CommandRiskAnalyzer.js'
import { SecretRedactor, get_shared_redactor } from './SecretRedactor.js'
import type { PathCategory, RiskAnalysis } from './index.js'

// Permission action per contracts §13 / DD §9.3
export type PermissionAction =
  | 'allow'                 // permitted — execute normally
  | 'announce_then_run'     // emit visible notice, then execute unless interrupted
  | 'ask_user'              // suspend; emit permission.prompt.requested
  | 'deny'                  // explicitly denied; return error
  | 'block'                 // return blocked outcome → task.blocked upstream
  | 'refuse'                // return AirError{kind:"policy_error"}; no execution

export interface PermissionDecision {
  action: PermissionAction
  reason: string
  requires_confirmation: boolean
  flags: string[]
  grant_scope?: string
  risk_level?: string
  fallback_result?: unknown
}

export interface PermissionContext {
  session_id: string
  project_id: string
  project_root: string
  agent_type: AgentType
  agent_id: string
  task_scope?: {
    allowed_paths?: string[]
    denied_paths?: string[]
    max_risk_score?: number
  }
  permission_profile?: PermissionProfile
}

export interface PermissionProfile {
  name: string
  allow_network: boolean
  allow_filesystem_write: boolean
  allow_execute: boolean
  allow_install: boolean
  max_risk_score: number
  allowed_tools?: string[]
  denied_tools?: string[]
}

// Layer order per DD §9.2
const LAYER_ORDER = [
  'capability',
  'profile',
  'task_scope',
  'risk',
  'credential_override',
  'user_prompt'
] as const

type LayerName = typeof LAYER_ORDER[number]

export class PermissionEngine {
  private path_classifier: PathClassifier
  private risk_analyzer: CommandRiskAnalyzer
  private redactor: SecretRedactor
  private decision_log: PermissionDecision[] = []

  constructor(project_root: string) {
    this.path_classifier = createPathClassifier(project_root)
    this.risk_analyzer = createCommandRiskAnalyzer(project_root)
    this.redactor = get_shared_redactor()
  }

  /**
   * Evaluate permission for a tool call.
   * Layered order 1–6 per DD §9.2.
   */
  async evaluate(
    tool_call: ToolCall,
    context: PermissionContext,
    tool_definition?: ToolDefinition
  ): Promise<PermissionDecision> {
    const layer_results: Array<{ layer: LayerName; decision: PermissionDecision }> = []

    // Layer 1: Capability check
    const capability_result = this.evaluate_capability(tool_call, context)
    layer_results.push({ layer: 'capability', decision: capability_result })
    if (capability_result.action !== 'allow') {
      return this.finalize_decision(capability_result, layer_results, tool_call)
    }

    // Layer 2: Permission profile check
    const profile_result = this.evaluate_profile(tool_call, context, tool_definition)
    layer_results.push({ layer: 'profile', decision: profile_result })
    if (profile_result.action !== 'allow') {
      return this.finalize_decision(profile_result, layer_results, tool_call)
    }

    // Layer 3: Task scope check
    const scope_result = this.evaluate_task_scope(tool_call, context)
    layer_results.push({ layer: 'task_scope', decision: scope_result })
    if (scope_result.action !== 'allow') {
      return this.finalize_decision(scope_result, layer_results, tool_call)
    }

    // Layer 4: Risk analysis check
    const risk_result = this.evaluate_risk(tool_call, context)
    layer_results.push({ layer: 'risk', decision: risk_result })
    if (risk_result.action !== 'allow') {
      return this.finalize_decision(risk_result, layer_results, tool_call)
    }

    // Layer 5: Credential override check
    const credential_result = this.evaluate_credential_override(tool_call, context)
    layer_results.push({ layer: 'credential_override', decision: credential_result })
    if (credential_result.action !== 'allow') {
      return this.finalize_decision(credential_result, layer_results, tool_call)
    }

    // Layer 6: User prompt check (placeholder - requires UI integration)
    const prompt_result: PermissionDecision = {
      action: 'allow',
      reason: 'no user prompt required',
      requires_confirmation: false,
      flags: []
    }
    layer_results.push({ layer: 'user_prompt', decision: prompt_result })

    return this.finalize_decision(prompt_result, layer_results, tool_call)
  }

  /**
   * Record a decision (writes permission.decision.recorded event).
   */
  async record(decision: PermissionDecision): Promise<{ ok: boolean; error?: string }> {
    this.decision_log.push(decision)

    // In production, this would write to the event log
    // For now, just track in memory
    return { ok: true }
  }

  /**
   * Get decision history.
   */
  get_history(): PermissionDecision[] {
    return [...this.decision_log]
  }

  // ============================================================================
  // Layer implementations
  // ============================================================================

  /**
   * Layer 1: Capability check
   * Check if the agent has the capability to use this tool.
   */
  private evaluate_capability(
    _tool_call: ToolCall,
    context: PermissionContext
  ): PermissionDecision {
    // For now, all tools are available to all agent types
    // In production, this would check the agent's capability manifest
    return {
      action: 'allow',
      reason: 'capability check passed',
      requires_confirmation: false,
      flags: ['capability_ok']
    }
  }

  /**
   * Layer 2: Permission profile check
   * Check against agent's permission profile.
   */
  private evaluate_profile(
    tool_call: ToolCall,
    context: PermissionContext,
    tool_definition?: ToolDefinition
  ): PermissionDecision {
    const profile = context.permission_profile
    if (!profile) {
      // No profile = allow with warning
      return {
        action: 'allow',
        reason: 'no permission profile, default allow',
        requires_confirmation: false,
        flags: ['no_profile']
      }
    }

    // Check tool whitelist/blacklist
    const tool_name = tool_call.name
    if (profile.allowed_tools && !profile.allowed_tools.includes(tool_name)) {
      return {
        action: 'deny',
        reason: `tool ${tool_name} not in allowed list`,
        requires_confirmation: false,
        flags: ['tool_not_allowed']
      }
    }

    if (profile.denied_tools?.includes(tool_name)) {
      return {
        action: 'deny',
        reason: `tool ${tool_name} explicitly denied`,
        requires_confirmation: false,
        flags: ['tool_denied']
      }
    }

    // Check tool category from definition
    const category = tool_definition?.category || 'unknown'
    const category_risk = this.get_category_risk(category)

    if (category === 'execute' && !profile.allow_execute) {
      return {
        action: 'deny',
        reason: 'execution not allowed by profile',
        requires_confirmation: false,
        flags: ['execution_denied']
      }
    }

    if ((category === 'filesystem' || category === 'network') && !profile.allow_filesystem_write) {
      return {
        action: 'announce_then_run',
        reason: 'write operations not allowed, announcing then running read-only',
        requires_confirmation: false,
        flags: ['downgraded_read_only']
      }
    }

    return {
      action: 'allow',
      reason: 'profile check passed',
      requires_confirmation: false,
      flags: ['profile_ok']
    }
  }

  /**
   * Layer 3: Task scope check
   * Check against task's allowed/denied paths.
   */
  private evaluate_task_scope(
    tool_call: ToolCall,
    context: PermissionContext
  ): PermissionDecision {
    const scope = context.task_scope
    if (!scope) {
      return {
        action: 'allow',
        reason: 'no task scope defined',
        requires_confirmation: false,
        flags: ['no_scope']
      }
    }

    // Extract paths from tool call arguments
    const paths = this.extract_paths_from_call(tool_call)
    if (paths.length === 0) {
      return {
        action: 'allow',
        reason: 'no paths to check in tool call',
        requires_confirmation: false,
        flags: ['no_paths']
      }
    }

    for (const path of paths) {
      const classification = this.path_classifier.classify(path)

      // Check denied paths
      if (scope.denied_paths?.some(denied => path.startsWith(denied))) {
        return {
          action: 'deny',
          reason: `path ${path} is in denied scope`,
          requires_confirmation: false,
          flags: ['scope_denied']
        }
      }

      // Check allowed paths (if defined, must match)
      if (scope.allowed_paths && scope.allowed_paths.length > 0) {
        const is_allowed = scope.allowed_paths.some(allowed => path.startsWith(allowed))
        if (!is_allowed) {
          return {
            action: 'deny',
            reason: `path ${path} not in allowed scope`,
            requires_confirmation: false,
            flags: ['scope_not_allowed']
          }
        }
      }
    }

    return {
      action: 'allow',
      reason: 'task scope check passed',
      requires_confirmation: false,
      flags: ['scope_ok']
    }
  }

  /**
   * Layer 4: Risk analysis check
   * Evaluate command risk and file operation risk.
   */
  private evaluate_risk(
    tool_call: ToolCall,
    context: PermissionContext
  ): PermissionDecision {
    const risk_score = this.calculate_risk_score(tool_call, context)
    const max_risk = context.task_scope?.max_risk_score ?? 70

    if (risk_score >= 90) {
      return {
        action: 'deny',
        reason: `risk score ${risk_score} exceeds threshold`,
        requires_confirmation: true,
        flags: ['high_risk']
      }
    }

    if (risk_score >= 70) {
      return {
        action: 'ask_user',
        reason: `risk score ${risk_score} requires user confirmation`,
        requires_confirmation: true,
        flags: ['medium_risk']
      }
    }

    if (risk_score >= 50) {
      return {
        action: 'announce_then_run',
        reason: `risk score ${risk_score}, allowing with audit announcement`,
        requires_confirmation: false,
        flags: ['low_risk', 'audit']
      }
    }

    return {
      action: 'allow',
      reason: `risk score ${risk_score} within acceptable range`,
      requires_confirmation: false,
      flags: ['risk_ok']
    }
  }

  /**
   * Layer 5: Credential override check
   * Check for credential/system-sensitive overrides.
   */
  private evaluate_credential_override(
    tool_call: ToolCall,
    _context: PermissionContext
  ): PermissionDecision {
    // Check if tool call exposes credentials
    const call_string = JSON.stringify(tool_call.arguments)
    if (this.redactor.contains_secrets(call_string)) {
      return {
        action: 'deny',
        reason: 'credential exposure detected',
        requires_confirmation: false,
        flags: ['credential_exposure']
      }
    }

    return {
      action: 'allow',
      reason: 'no credential override triggered',
      requires_confirmation: false,
      flags: ['credential_ok']
    }
  }

  // ============================================================================
  // Helpers
  // ============================================================================

  private calculate_risk_score(tool_call: ToolCall, context: PermissionContext): number {
    let score = 0
    const category = tool_call.name.split('.')[0] // e.g., 'fs', 'shell', 'git'

    // Base risk by tool category
    const category_scores: Record<string, number> = {
      fs: 20,
      shell: 60,
      git: 30,
      project: 10,
      artifact: 15,
      context: 5,
      permission: 5,
      doctor: 10
    }
    score += category_scores[category] || 20

    // Command-specific risk for shell commands
    if (tool_call.name === 'shell.run' && tool_call.arguments.command) {
      const analysis = this.risk_analyzer.analyze(tool_call.arguments.command as string)
      score = Math.max(score, analysis.risk_score)
    }

    // Path-specific risk
    const paths = this.extract_paths_from_call(tool_call)
    for (const path of paths) {
      const classification = this.path_classifier.classify(path)
      if (classification.category === 'system_sensitive') score += 30
      if (classification.category === 'project_git' || classification.category === 'project_air_shared') score += 20
      if (classification.is_symlink_escape) score += 40
    }

    return Math.min(score, 100)
  }

  private extract_paths_from_call(tool_call: ToolCall): string[] {
    const paths: string[] = []
    const args = tool_call.arguments

    // Common path argument names
    const path_keys = ['path', 'file', 'file_path', 'dir', 'directory', 'target', 'source', 'destination']

    const extract = (obj: unknown) => {
      if (typeof obj === 'string') {
        paths.push(obj)
      } else if (Array.isArray(obj)) {
        obj.forEach(extract)
      } else if (typeof obj === 'object' && obj !== null) {
        for (const key of path_keys) {
          if (key in obj) {
            extract((obj as Record<string, unknown>)[key])
          }
        }
      }
    }

    extract(args)
    return paths
  }

  private get_category_risk(category: string): number {
    const risks: Record<string, number> = {
      filesystem: 30,
      network: 40,
      execute: 70,
      read: 5,
      project: 10,
      context: 5,
      permission: 20,
      doctor: 10
    }
    return risks[category] || 20
  }

  private finalize_decision(
    decision: PermissionDecision,
    layers: Array<{ layer: LayerName; decision: PermissionDecision }>,
    tool_call: ToolCall
  ): PermissionDecision {
    // Log the decision
    this.decision_log.push({
      ...decision,
      flags: [...decision.flags, ...layers.map(l => l.layer)]
    })

    // Redact sensitive data from decision
    return {
      ...decision,
      reason: this.redactor.redact(decision.reason).redacted
    }
  }
}

export function createPermissionEngine(project_root: string): PermissionEngine {
  return new PermissionEngine(project_root)
}

// Default profiles per DD §9.2
export const DEFAULT_PROFILES: Record<AgentType, PermissionProfile> = {
  executor: {
    name: 'executor',
    allow_network: true,
    allow_filesystem_write: true,
    allow_execute: true,
    allow_install: false,
    max_risk_score: 70
  },
  reviewer: {
    name: 'reviewer',
    allow_network: true,
    allow_filesystem_write: false,
    allow_execute: false,
    allow_install: false,
    max_risk_score: 30
  },
  debugger: {
    name: 'debugger',
    allow_network: true,
    allow_filesystem_write: true,
    allow_execute: true,
    allow_install: false,
    max_risk_score: 60
  },
  compactor: {
    name: 'compactor',
    allow_network: false,
    allow_filesystem_write: true,
    allow_execute: false,
    allow_install: false,
    max_risk_score: 20
  },
  experience_miner: {
    name: 'experience_miner',
    allow_network: true,
    allow_filesystem_write: false,
    allow_execute: false,
    allow_install: false,
    max_risk_score: 30
  }
}