// src/core/security/SecurityRealm.ts

import {
  Supervisor,
  type ISupervisionPolicy,
  type SupervisionAction,
  type ISupervisionError,
  DEFAULT_SUPERVISION_POLICY,
} from '../Supervision';
import type { IExitSignal } from '../../engine/mailbox/IMailbox';
import type { ICapabilityToken, CapabilityRight } from './ICapabilityToken';
import type {
  ISupervisorSecurityPolicy,
  IPolicySectionBase,
  VerificationDepth,
  AuditLevel,
} from './ISupervisorSecurityPolicy';
import type {
  ISecurityRealm,
  MessageDirection,
  IVerificationResult,
  IAttenuationOptions,
  IMintOptions,
} from './ISecurityRealm';
import type {
  ISpawnPolicyService,
  ISpawnRequest,
  ISpawnDecision,
} from './ISpawnPolicyService';
import type { IAuditService } from './IAuditService';
import type { IPolicyEngine } from './IPolicyEngine';
import { DefaultSpawnPolicy } from './DefaultSpawnPolicy';
import { DEFAULT_SECURITY_POLICY } from './defaults';
import { NullAuditService } from './SecurityAuditService';

/**
 * SecurityRealm - Composes Supervisor + SecurityPolicy.
 *
 * Joe Armstrong's insight: "Supervision tree = Security tree."
 * The supervisor that handles errors is the same entity that
 * enforces security policy for its children.
 *
 * Expert consensus (2025-12-14):
 * - NO interface split - unified per Vision 27
 * - Composes existing Supervisor - no duplication
 * - Switch dispatch for policy sections - value objects
 *
 * See: Vision/27-SECURITY-FEDERATION-DRAFT.md
 */
export class SecurityRealm implements ISecurityRealm {
  private readonly _supervisor: Supervisor;
  private readonly _policy: ISupervisorSecurityPolicy;
  private readonly _spawnPolicy: ISpawnPolicyService;
  private readonly _auditService: IAuditService;
  private readonly _policyEngine: IPolicyEngine | null;
  private readonly _mintedCapabilities = new Map<string, ICapabilityToken>();
  private readonly _revokedCapabilities = new Set<string>();

  constructor(
    supervisionPolicy: ISupervisionPolicy = DEFAULT_SUPERVISION_POLICY,
    securityPolicy: ISupervisorSecurityPolicy = DEFAULT_SECURITY_POLICY,
    spawnPolicy: ISpawnPolicyService = new DefaultSpawnPolicy(),
    auditService: IAuditService = new NullAuditService(),
    policyEngine?: IPolicyEngine,
  ) {
    this._supervisor = new Supervisor(supervisionPolicy);
    this._policy = securityPolicy;
    this._spawnPolicy = spawnPolicy;
    this._auditService = auditService;
    this._policyEngine = policyEngine ?? null;
  }

  /** ADR-025: Pluggable policy engine for Ring 0 message evaluation */
  get policyEngine(): IPolicyEngine | undefined {
    return this._policyEngine ?? undefined;
  }

  /** Get the audit service (for querying audit trail) */
  get auditService(): IAuditService {
    return this._auditService;
  }

  /** Realm identifier */
  get realm(): string {
    return this._policy.realm;
  }

  // ═══════════════════════════════════════════════════════════════════════
  // SUPERVISION (delegated to existing Supervisor)
  // ═══════════════════════════════════════════════════════════════════════

  async handleError(error: ISupervisionError): Promise<SupervisionAction> {
    return this._supervisor.handleError(error);
  }

  async handleExitSignal(signal: IExitSignal): Promise<SupervisionAction> {
    return this._supervisor.handleExitSignal(signal);
  }

  getRestartDelayMs(): number {
    return this._supervisor.getRestartDelayMs();
  }

  isRestartAllStrategy(): boolean {
    return this._supervisor.isRestartAllStrategy();
  }

  resetChild(childId: string): void {
    this._supervisor.resetChild(childId);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // CAPABILITY MANAGEMENT (Mark Miller's model)
  // ═══════════════════════════════════════════════════════════════════════

  /**
   * Mint a new capability for a target.
   */
  mintCapability(
    target: string,
    rights: CapabilityRight[],
    options?: IMintOptions
  ): ICapabilityToken {
    const now = Date.now();
    const ttlMs = options?.ttlMs ?? 3600000; // Default: 1 hour

    const token: ICapabilityToken = {
      id: `cap-${now}-${Math.random().toString(36).slice(2, 10)}`,
      issuer: this._policy.realm,
      subject: target,
      rights: [...rights], // Defensive copy (Rich Hickey)
      caveats: options?.caveats ? { ...options.caveats } : {},
      issuedAt: now,
      expiresAt: now + ttlMs,
    };

    this._mintedCapabilities.set(token.id, token);

    // Audit: capability-mint is security-critical (always logged)
    this._auditService.record(
      {
        principal: this._policy.realm,
        capability: token,
        action: 'capability-mint',
        target,
        sourceRealm: this._policy.realm,
        targetRealm: this._policy.realm,
        outcome: 'success',
      },
      'full' // Security-critical: always full audit
    );

    return token;
  }

  /**
   * Attenuate a capability (reduce rights).
   *
   * Mark Miller: "Capabilities can only be attenuated, never amplified."
   */
  attenuateCapability(
    token: ICapabilityToken,
    options: IAttenuationOptions
  ): ICapabilityToken {
    // Filter out removed rights
    const newRights = token.rights.filter(
      (r) => !options.removeRights?.includes(r)
    );

    // Expiration can only be reduced, never extended
    const newExpiresAt = options.expiresAt
      ? Math.min(options.expiresAt, token.expiresAt)
      : token.expiresAt;

    const attenuated: ICapabilityToken = {
      id: `cap-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`,
      issuer: this._policy.realm,
      subject: token.subject,
      rights: newRights,
      caveats: { ...token.caveats, ...options.addCaveats },
      issuedAt: Date.now(),
      expiresAt: newExpiresAt,
      parent: token.id, // Track delegation chain
    };

    this._mintedCapabilities.set(attenuated.id, attenuated);

    // Audit: capability-attenuate (logged per childToParent auditLevel)
    this._auditService.record(
      {
        principal: this._policy.realm,
        capability: attenuated,
        action: 'capability-attenuate',
        target: token.subject,
        sourceRealm: this._policy.realm,
        targetRealm: this._policy.realm,
        outcome: 'success',
      },
      this._policy.childToParent.auditLevel
    );

    return attenuated;
  }

  /**
   * Check if a capability is valid.
   *
   * Mark Miller: "Capabilities must be unforgeable."
   * We verify against our stored records, not the passed token's fields.
   */
  isCapabilityValid(token: ICapabilityToken): boolean {
    // CRITICAL: Check if this realm minted this capability
    // (Prevents forgery attacks - Mark Miller)
    const storedToken = this._mintedCapabilities.get(token.id);
    if (!storedToken) {
      // Unknown capability - not minted by this realm = FORGERY
      return false;
    }

    // Check if revoked
    if (this._revokedCapabilities.has(token.id)) {
      return false;
    }

    // Check if expired - use STORED expiry, not passed token's expiry
    // (Prevents expiry tampering attacks - Barbara Liskov)
    if (Date.now() > storedToken.expiresAt) {
      return false;
    }

    // Check delegation chain if parent exists
    if (storedToken.parent && this._revokedCapabilities.has(storedToken.parent)) {
      return false;
    }

    return true;
  }

  /**
   * Revoke a capability.
   */
  revokeCapability(tokenId: string): void {
    this._revokedCapabilities.add(tokenId);
    this._mintedCapabilities.delete(tokenId);

    // Audit: capability-revoke is security-critical (always logged)
    this._auditService.record(
      {
        principal: this._policy.realm,
        action: 'capability-revoke',
        target: tokenId,
        sourceRealm: this._policy.realm,
        targetRealm: this._policy.realm,
        outcome: 'success',
      },
      'full' // Security-critical: always full audit
    );

    // Propagate to children if configured
    if (this._policy.revocation.propagateToChildren) {
      // Find and revoke all tokens with this parent
      // Use Array.from for ES5 compatibility
      const entries = Array.from(this._mintedCapabilities.entries());
      for (const [id, token] of entries) {
        if (token.parent === tokenId) {
          this.revokeCapability(id);
        }
      }
    }
  }

  // ═══════════════════════════════════════════════════════════════════════
  // MESSAGE VERIFICATION
  // ═══════════════════════════════════════════════════════════════════════

  /**
   * Verify if a message is allowed by policy.
   */
  verifyMessage(
    direction: MessageDirection,
    capability?: ICapabilityToken,
    context?: { principal?: string; target?: string; command?: string }
  ): IVerificationResult {
    const policySection = this._getPolicySection(direction);
    const auditLevel = this._getAuditLevel(direction);

    // Helper to audit and return result
    const auditAndReturn = (result: IVerificationResult): IVerificationResult => {
      this._auditService.record(
        {
          principal: context?.principal ?? 'unknown',
          capability: capability,
          action: 'capability-verify',
          target: context?.target ?? capability?.subject ?? 'unknown',
          command: context?.command,
          sourceRealm: this._policy.realm,
          targetRealm: this._policy.realm,
          outcome: result.allowed ? 'success' : 'denied',
          denialReason: result.reason,
        },
        auditLevel
      );
      return result;
    };

    // ═══════════════════════════════════════════════════════════════════════
    // PARENT-TO-CHILD IS ALWAYS TRUSTED (Joe Armstrong's supervision model)
    // This check must come BEFORE capability requirements.
    //
    // Joe Armstrong (Erlang, "Programming Erlang"):
    //   "The parent is always trusted. It created the child, it supervises
    //    the child, it can send any message to the child."
    // ═══════════════════════════════════════════════════════════════════════
    if (direction === 'parent-to-child') {
      return auditAndReturn({ allowed: true });
    }

    // Check if capability is required
    if (policySection.requireCapability && !capability) {
      return auditAndReturn({ allowed: false, reason: 'Missing capability token' });
    }

    // Validate capability if provided
    if (capability) {
      if (!this.isCapabilityValid(capability)) {
        return auditAndReturn({ allowed: false, reason: 'Capability is invalid or revoked' });
      }

      // For directions with verification depth, check it
      const verificationDepth = this._getVerificationDepth(direction);
      if (verificationDepth === 'presence') {
        return auditAndReturn({ allowed: true });
      }

      // Phase 2-3: Signature/chain verification
      // Joe Armstrong: "Fail fast" - deny what we cannot verify
      // Mark Miller: "Never silently allow what you can't verify"
      if (verificationDepth === 'signature') {
        // Cryptographic signature verification not yet implemented
        // SECURITY: DENY rather than silently allow
        return auditAndReturn({
          allowed: false,
          reason: 'Signature verification not yet implemented (Phase 2)',
        });
      }

      if (verificationDepth === 'full-chain') {
        // Full delegation chain verification not yet implemented
        // SECURITY: DENY rather than silently allow
        return auditAndReturn({
          allowed: false,
          reason: 'Full-chain verification not yet implemented (Phase 3)',
        });
      }
    }

    return auditAndReturn({ allowed: true });
  }

  // ═══════════════════════════════════════════════════════════════════════
  // SPAWN POLICY (Vision 27 Section 4.3)
  // ═══════════════════════════════════════════════════════════════════════

  /**
   * Evaluate a spawn request against the security policy.
   *
   * Called before creating a child component to check if:
   * - The component type is allowed
   * - The props are acceptable (may be sanitized)
   * - The requestor has permission to spawn
   *
   * @param request - Spawn request details
   * @returns Decision with allowed/denied and optional modifications
   */
  evaluateSpawn(request: ISpawnRequest): ISpawnDecision {
    return this._spawnPolicy.evaluate(request);
  }

  // ═══════════════════════════════════════════════════════════════════════
  // PRIVATE HELPERS
  // ═══════════════════════════════════════════════════════════════════════

  /**
   * Get the base policy section for a direction.
   * Returns only the common fields (requireCapability).
   *
   * Carl Hewitt: "Policy sections are VALUE OBJECTS.
   * Switch is appropriate for value-object dispatch."
   */
  private _getPolicySection(direction: MessageDirection): IPolicySectionBase {
    switch (direction) {
      case 'child-to-parent':
        return this._policy.childToParent;
      case 'parent-to-child':
        return this._policy.parentToChild;
      case 'sibling':
        return this._policy.siblingToSibling;
      case 'external':
        return this._policy.external;
      default:
        return this._policy.external; // Most restrictive for unknown
    }
  }

  /**
   * Get verification depth for directions that support it.
   * Parent-to-child has no verification depth (parent is trusted).
   */
  private _getVerificationDepth(direction: MessageDirection): VerificationDepth {
    switch (direction) {
      case 'child-to-parent':
        return this._policy.childToParent.verificationDepth;
      case 'sibling':
        return this._policy.siblingToSibling.verificationDepth;
      case 'external':
        return this._policy.external.verificationDepth;
      case 'parent-to-child':
      default:
        return 'presence'; // Parent is trusted, no verification needed
    }
  }

  /**
   * Get audit level for a direction.
   *
   * Vision 27 Section 7.3:
   * - External: always 'full'
   * - Child-to-parent: per policy
   * - Parent-to-child: no audit level (uses 'none')
   * - Sibling: per policy (uses external as proxy)
   */
  private _getAuditLevel(direction: MessageDirection): AuditLevel {
    switch (direction) {
      case 'child-to-parent':
        return this._policy.childToParent.auditLevel;
      case 'external':
        return this._policy.external.auditLevel;
      case 'sibling':
        // Sibling uses external audit level (most restrictive)
        return this._policy.external.auditLevel;
      case 'parent-to-child':
      default:
        return 'none'; // Parent-to-child not typically audited
    }
  }
}
