/**
 * Cache Manager — Hash-basierte Prose-Block-Cache
 *
 * Deterministischer Cache fuer LLM-generierte Prosa-Bloecke.
 * Kein TTL-basiertes Raten — stattdessen Hash-basierte Invalidierung.
 *
 * Cache-Key = SHA-256 ueber alle Eingabeparameter.
 * Aendert sich ein Eingabewert → neuer Hash → Cache-Miss → Neu-Generierung.
 */

import type { AllowedFacts } from './allowed-facts'
import type { NarrativeTags } from './narrative-tags'
import type { ProseBlockOutput } from './prose-validator'

// ============================================================================
// Types
// ============================================================================

export interface CacheEntry {
  block: ProseBlockOutput
  createdAt: string
  hitCount: number
  cacheKey: string
}

export interface CacheKeyParams {
  allowedFacts: AllowedFacts
  templateVersion: string
  terminologyVersion: string
  narrativeTags: NarrativeTags
  promptHash: string
  blockType: string
  sectionName: string
}

export interface CacheStats {
  totalEntries: number
  totalHits: number
  totalMisses: number
  hitRate: number
  oldestEntry: string | null
  newestEntry: string | null
}

// ============================================================================
// SHA-256 (Browser-kompatibel via SubtleCrypto)
// ============================================================================

/**
 * Berechnet SHA-256 Hash eines Strings.
 * Nutzt SubtleCrypto (verfuegbar in Node.js 15+ und allen modernen Browsern).
 */
async function sha256(input: string): Promise<string> {
  // In Next.js API Routes laeuft Node.js — nutze crypto
  if (typeof globalThis.crypto?.subtle !== 'undefined') {
    const encoder = new TextEncoder()
    const data = encoder.encode(input)
    const hashBuffer = await globalThis.crypto.subtle.digest('SHA-256', data)
    const hashArray = Array.from(new Uint8Array(hashBuffer))
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
  }

  // Fallback: Node.js crypto
  try {
    const { createHash } = await import('crypto')
    return createHash('sha256').update(input).digest('hex')
  } catch {
    // Letzer Fallback: Einfacher Hash (nicht kryptographisch)
    return simpleHash(input)
  }
}

/**
 * Synchrone SHA-256 Berechnung (Node.js only).
 */
function sha256Sync(input: string): string {
  try {
    // eslint-disable-next-line @typescript-eslint/no-require-imports
    const crypto = require('crypto')
    return crypto.createHash('sha256').update(input).digest('hex')
  } catch {
    return simpleHash(input)
  }
}

/**
 * Einfacher nicht-kryptographischer Hash als Fallback.
 */
function simpleHash(input: string): string {
  let hash = 0
  for (let i = 0; i < input.length; i++) {
    const char = input.charCodeAt(i)
    hash = ((hash << 5) - hash) + char
    hash = hash & hash
  }
  return Math.abs(hash).toString(16).padStart(16, '0')
}

// ============================================================================
// Cache Key Computation
// ============================================================================

/**
 * Berechnet den deterministischen Cache-Key.
 * Sortiert Keys um konsistente Serialisierung zu gewaehrleisten.
 */
export async function computeCacheKey(params: CacheKeyParams): Promise<string> {
  const payload = JSON.stringify(params, Object.keys(params).sort())
  return sha256(payload)
}

/**
 * Synchrone Variante fuer Cache-Key (Node.js).
 */
export function computeCacheKeySync(params: CacheKeyParams): string {
  const payload = JSON.stringify(params, Object.keys(params).sort())
  return sha256Sync(payload)
}

// ============================================================================
// In-Memory Cache
// ============================================================================

/**
 * In-Memory Cache fuer Prose-Bloecke.
 *
 * Sicherheitsmechanismen:
 * - Max Eintraege (Speicher-Limit)
 * - TTL als zusaetzlicher Sicherheitsmechanismus (24h default)
 * - LRU-artige Bereinigung bei Overflow
 */
export class ProseCacheManager {
  private cache = new Map<string, CacheEntry>()
  private hits = 0
  private misses = 0
  private readonly maxEntries: number
  private readonly ttlMs: number

  constructor(options?: { maxEntries?: number; ttlHours?: number }) {
    this.maxEntries = options?.maxEntries ?? 500
    this.ttlMs = (options?.ttlHours ?? 24) * 60 * 60 * 1000
  }

  /**
   * Sucht einen gecachten Block.
   */
  async get(params: CacheKeyParams): Promise<ProseBlockOutput | null> {
    const key = await computeCacheKey(params)
    return this.getByKey(key)
  }

  /**
   * Sucht synchron (Node.js).
   */
  getSync(params: CacheKeyParams): ProseBlockOutput | null {
    const key = computeCacheKeySync(params)
    return this.getByKey(key)
  }

  /**
   * Speichert einen Block im Cache.
   */
  async set(params: CacheKeyParams, block: ProseBlockOutput): Promise<void> {
    const key = await computeCacheKey(params)
    this.setByKey(key, block)
  }

  /**
   * Speichert synchron (Node.js).
   */
  setSync(params: CacheKeyParams, block: ProseBlockOutput): void {
    const key = computeCacheKeySync(params)
    this.setByKey(key, block)
  }

  /**
   * Gibt Cache-Statistiken zurueck.
   */
  getStats(): CacheStats {
    const entries = Array.from(this.cache.values())
    const total = this.hits + this.misses

    return {
      totalEntries: this.cache.size,
      totalHits: this.hits,
      totalMisses: this.misses,
      hitRate: total > 0 ? this.hits / total : 0,
      oldestEntry: entries.length > 0
        ? entries.reduce((a, b) => a.createdAt < b.createdAt ? a : b).createdAt
        : null,
      newestEntry: entries.length > 0
        ? entries.reduce((a, b) => a.createdAt > b.createdAt ? a : b).createdAt
        : null,
    }
  }

  /**
   * Loescht alle Eintraege.
   */
  clear(): void {
    this.cache.clear()
    this.hits = 0
    this.misses = 0
  }

  /**
   * Loescht abgelaufene Eintraege.
   */
  cleanup(): number {
    const now = Date.now()
    let removed = 0
    for (const [key, entry] of this.cache.entries()) {
      if (now - new Date(entry.createdAt).getTime() > this.ttlMs) {
        this.cache.delete(key)
        removed++
      }
    }
    return removed
  }

  // ========================================================================
  // Private
  // ========================================================================

  private getByKey(key: string): ProseBlockOutput | null {
    const entry = this.cache.get(key)

    if (!entry) {
      this.misses++
      return null
    }

    // TTL pruefen
    if (Date.now() - new Date(entry.createdAt).getTime() > this.ttlMs) {
      this.cache.delete(key)
      this.misses++
      return null
    }

    entry.hitCount++
    this.hits++
    return entry.block
  }

  private setByKey(key: string, block: ProseBlockOutput): void {
    // Bei Overflow: aeltesten Eintrag entfernen
    if (this.cache.size >= this.maxEntries) {
      this.evictOldest()
    }

    this.cache.set(key, {
      block,
      createdAt: new Date().toISOString(),
      hitCount: 0,
      cacheKey: key,
    })
  }

  private evictOldest(): void {
    let oldestKey: string | null = null
    let oldestTime = Infinity

    for (const [key, entry] of this.cache.entries()) {
      const time = new Date(entry.createdAt).getTime()
      if (time < oldestTime) {
        oldestTime = time
        oldestKey = key
      }
    }

    if (oldestKey) {
      this.cache.delete(oldestKey)
    }
  }
}

// ============================================================================
// Checksum Utils (fuer Data Block Integritaet)
// ============================================================================

/**
 * Berechnet Integritaets-Checksum ueber Daten.
 */
export async function computeChecksum(data: unknown): Promise<string> {
  const serialized = JSON.stringify(data, Object.keys(data as Record<string, unknown>).sort())
  return sha256(serialized)
}

/**
 * Synchrone Checksum-Berechnung.
 */
export function computeChecksumSync(data: unknown): string {
  const serialized = JSON.stringify(data, Object.keys(data as Record<string, unknown>).sort())
  return sha256Sync(serialized)
}

/**
 * Verifiziert eine Checksum gegen Daten.
 */
export async function verifyChecksum(data: unknown, expectedChecksum: string): Promise<boolean> {
  const actual = await computeChecksum(data)
  return actual === expectedChecksum
}
