/**
 * DelimiterHardener — Cryptographic delimiter hardening.
 *
 * Generates session-unique, unpredictable delimiters using HMAC-SHA256.
 * Wraps content with these delimiters and provides verification to detect
 * if an attacker has injected or modified delimiter boundaries.
 */

import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto'
import type { ShieldXConfig } from '../types/detection.js'

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

/** Result of delimiter verification */
export interface DelimiterVerificationResult {
  readonly valid: boolean
  readonly contentExtracted: string | null
  readonly delimiterMatch: boolean
  readonly integrityHash: string | null
}

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

/** Secret used for HMAC if none provided */
const DEFAULT_DELIMITER_SECRET = 'shieldx-delimiter-default-secret-change-me'

/** Delimiter prefix/suffix markers */
const DELIMITER_PREFIX = '<<<SHIELDX_DELIM_'
const DELIMITER_SUFFIX = '>>>'
const INTEGRITY_PREFIX = '[INTEGRITY:'
const INTEGRITY_SUFFIX = ']'

// ---------------------------------------------------------------------------
// Implementation
// ---------------------------------------------------------------------------

/**
 * Cryptographic Delimiter Hardener.
 *
 * Generates session-unique delimiters that attackers cannot predict or forge.
 * HMAC-SHA256 ensures the delimiter is tied to the session and cannot be
 * derived without knowing the secret.
 */
export class DelimiterHardener {
  private readonly _config: ShieldXConfig
  private readonly secret: string

  /** Access the active configuration */
  get config(): ShieldXConfig { return this._config }

  constructor(config: ShieldXConfig, secret?: string) {
    this._config = config
    this.secret = secret ?? DEFAULT_DELIMITER_SECRET
  }

  /**
   * Generate a cryptographically hardened delimiter for a session.
   *
   * @param sessionId - Session identifier for deterministic generation
   * @returns Session-unique delimiter string
   */
  generateDelimiter(sessionId: string): string {
    const hmac = createHmac('sha256', this.secret)
    hmac.update(`delimiter:${sessionId}:${this.getSalt()}`)
    const hash = hmac.digest('hex').slice(0, 24)
    return `${DELIMITER_PREFIX}${hash}${DELIMITER_SUFFIX}`
  }

  /**
   * Wrap content with the given delimiter and an integrity hash.
   *
   * @param content - Content to wrap
   * @param delimiter - Delimiter generated by generateDelimiter
   * @returns Wrapped content with delimiter boundaries and integrity hash
   */
  wrapContent(content: string, delimiter: string): string {
    const integrityHash = this.computeIntegrityHash(content, delimiter)

    return [
      delimiter,
      `${INTEGRITY_PREFIX}${integrityHash}${INTEGRITY_SUFFIX}`,
      content,
      delimiter,
    ].join('\n')
  }

  /**
   * Verify that wrapped content has valid delimiters and intact integrity.
   *
   * @param wrapped - The wrapped content string
   * @param delimiter - The expected delimiter
   * @returns Verification result with extracted content if valid
   */
  verifyDelimiter(
    wrapped: string,
    delimiter: string,
  ): DelimiterVerificationResult {
    // Check delimiter presence
    const lines = wrapped.split('\n')
    if (lines.length < 4) {
      return this.buildVerificationResult(false, null, false, null)
    }

    const firstLine = lines[0] ?? ''
    const lastLine = lines[lines.length - 1] ?? ''

    // Verify delimiter boundaries
    const delimiterMatch =
      firstLine === delimiter && lastLine === delimiter

    if (!delimiterMatch) {
      return this.buildVerificationResult(false, null, false, null)
    }

    // Extract integrity hash
    const integrityLine = lines[1] ?? ''
    const extractedHash = this.extractIntegrityHash(integrityLine)

    if (extractedHash.length === 0) {
      return this.buildVerificationResult(false, null, true, null)
    }

    // Extract content (between integrity line and closing delimiter)
    const contentLines = lines.slice(2, lines.length - 1)
    const content = contentLines.join('\n')

    // Verify integrity
    const expectedHash = this.computeIntegrityHash(content, delimiter)
    const valid = this.safeCompare(extractedHash, expectedHash)

    return this.buildVerificationResult(
      valid,
      valid ? content : null,
      true,
      extractedHash,
    )
  }

  /**
   * Generate a fresh delimiter with a random component (non-session-bound).
   * Useful for one-time wrapping where session binding is not needed.
   *
   * @returns A random delimiter string
   */
  generateRandomDelimiter(): string {
    const random = randomBytes(12).toString('hex')
    return `${DELIMITER_PREFIX}${random}${DELIMITER_SUFFIX}`
  }

  /** Compute HMAC integrity hash of content + delimiter */
  private computeIntegrityHash(content: string, delimiter: string): string {
    const hmac = createHmac('sha256', this.secret)
    hmac.update(`${delimiter}:${content}`)
    return hmac.digest('hex').slice(0, 16)
  }

  /** Extract integrity hash from the integrity line */
  private extractIntegrityHash(line: string): string {
    if (!line.startsWith(INTEGRITY_PREFIX) || !line.endsWith(INTEGRITY_SUFFIX)) {
      return ''
    }
    return line.slice(INTEGRITY_PREFIX.length, -INTEGRITY_SUFFIX.length)
  }

  /** Timing-safe string comparison to prevent timing attacks */
  private safeCompare(a: string, b: string): boolean {
    if (a.length !== b.length) return false
    try {
      return timingSafeEqual(Buffer.from(a), Buffer.from(b))
    } catch {
      return false
    }
  }

  /** Get a time-based salt (rotates hourly) */
  private getSalt(): string {
    return Math.floor(Date.now() / 3600000).toString()
  }

  /** Build an immutable verification result */
  private buildVerificationResult(
    valid: boolean,
    contentExtracted: string | null,
    delimiterMatch: boolean,
    integrityHash: string | null,
  ): DelimiterVerificationResult {
    return Object.freeze({
      valid,
      contentExtracted,
      delimiterMatch,
      integrityHash,
    })
  }
}
