// =============================================================================
// TOM Rules Engine
// Evaluates control applicability based on company context
// =============================================================================

import {
  ControlLibraryEntry,
  ApplicabilityCondition,
  ControlApplicability,
  RulesEngineResult,
  RulesEngineEvaluationContext,
  DerivedTOM,
  EvidenceDocument,
  GapAnalysisResult,
  MissingControl,
  PartialControl,
  MissingEvidence,
  ConditionOperator,
} from './types'
import { getAllControls, getControlById } from './controls/loader'

// =============================================================================
// RULES ENGINE CLASS
// =============================================================================

export class TOMRulesEngine {
  private controls: ControlLibraryEntry[]

  constructor() {
    this.controls = getAllControls()
  }

  /**
   * Evaluate all controls against the current context
   */
  evaluateControls(context: RulesEngineEvaluationContext): RulesEngineResult[] {
    return this.controls.map((control) => this.evaluateControl(control, context))
  }

  /**
   * Evaluate a single control against the context
   */
  evaluateControl(
    control: ControlLibraryEntry,
    context: RulesEngineEvaluationContext
  ): RulesEngineResult {
    // Sort conditions by priority (highest first)
    const sortedConditions = [...control.applicabilityConditions].sort(
      (a, b) => b.priority - a.priority
    )

    // Evaluate conditions in priority order
    for (const condition of sortedConditions) {
      const matches = this.evaluateCondition(condition, context)
      if (matches) {
        return {
          controlId: control.id,
          applicability: condition.result,
          reason: this.formatConditionReason(condition, context),
          matchedCondition: condition,
        }
      }
    }

    // No condition matched, use default applicability
    return {
      controlId: control.id,
      applicability: control.defaultApplicability,
      reason: 'Standard-Anwendbarkeit (keine spezifische Bedingung erfüllt)',
    }
  }

  /**
   * Evaluate a single condition
   */
  private evaluateCondition(
    condition: ApplicabilityCondition,
    context: RulesEngineEvaluationContext
  ): boolean {
    const value = this.getFieldValue(condition.field, context)

    if (value === undefined || value === null) {
      return false
    }

    return this.evaluateOperator(condition.operator, value, condition.value)
  }

  /**
   * Get a nested field value from the context
   */
  private getFieldValue(
    fieldPath: string,
    context: RulesEngineEvaluationContext
  ): unknown {
    const parts = fieldPath.split('.')
    let current: unknown = context

    for (const part of parts) {
      if (current === null || current === undefined) {
        return undefined
      }
      if (typeof current === 'object') {
        current = (current as Record<string, unknown>)[part]
      } else {
        return undefined
      }
    }

    return current
  }

  /**
   * Evaluate an operator with given values
   */
  private evaluateOperator(
    operator: ConditionOperator,
    actualValue: unknown,
    expectedValue: unknown
  ): boolean {
    switch (operator) {
      case 'EQUALS':
        return actualValue === expectedValue

      case 'NOT_EQUALS':
        return actualValue !== expectedValue

      case 'CONTAINS':
        if (Array.isArray(actualValue)) {
          return actualValue.includes(expectedValue)
        }
        if (typeof actualValue === 'string' && typeof expectedValue === 'string') {
          return actualValue.includes(expectedValue)
        }
        return false

      case 'GREATER_THAN':
        if (typeof actualValue === 'number' && typeof expectedValue === 'number') {
          return actualValue > expectedValue
        }
        return false

      case 'IN':
        if (Array.isArray(expectedValue)) {
          return expectedValue.includes(actualValue)
        }
        return false

      default:
        return false
    }
  }

  /**
   * Format a human-readable reason for the condition match
   */
  private formatConditionReason(
    condition: ApplicabilityCondition,
    context: RulesEngineEvaluationContext
  ): string {
    const fieldValue = this.getFieldValue(condition.field, context)
    const fieldLabel = this.getFieldLabel(condition.field)

    switch (condition.operator) {
      case 'EQUALS':
        return `${fieldLabel} ist "${this.formatValue(fieldValue)}"`

      case 'NOT_EQUALS':
        return `${fieldLabel} ist nicht "${this.formatValue(condition.value)}"`

      case 'CONTAINS':
        return `${fieldLabel} enthält "${this.formatValue(condition.value)}"`

      case 'GREATER_THAN':
        return `${fieldLabel} ist größer als ${this.formatValue(condition.value)}`

      case 'IN':
        return `${fieldLabel} ("${this.formatValue(fieldValue)}") ist in [${Array.isArray(condition.value) ? condition.value.join(', ') : condition.value}]`

      default:
        return `Bedingung erfüllt: ${condition.field} ${condition.operator} ${this.formatValue(condition.value)}`
    }
  }

  /**
   * Get a human-readable label for a field path
   */
  private getFieldLabel(fieldPath: string): string {
    const labels: Record<string, string> = {
      'companyProfile.role': 'Unternehmensrolle',
      'companyProfile.size': 'Unternehmensgröße',
      'dataProfile.hasSpecialCategories': 'Besondere Datenkategorien',
      'dataProfile.processesMinors': 'Verarbeitung von Minderjährigen-Daten',
      'dataProfile.dataVolume': 'Datenvolumen',
      'dataProfile.thirdCountryTransfers': 'Drittlandübermittlungen',
      'architectureProfile.hostingModel': 'Hosting-Modell',
      'architectureProfile.hostingLocation': 'Hosting-Standort',
      'architectureProfile.multiTenancy': 'Mandantentrennung',
      'architectureProfile.hasSubprocessors': 'Unterauftragsverarbeiter',
      'architectureProfile.encryptionAtRest': 'Verschlüsselung ruhender Daten',
      'securityProfile.hasMFA': 'Multi-Faktor-Authentifizierung',
      'securityProfile.hasSSO': 'Single Sign-On',
      'securityProfile.hasPAM': 'Privileged Access Management',
      'riskProfile.protectionLevel': 'Schutzbedarf',
      'riskProfile.dsfaRequired': 'DSFA erforderlich',
      'riskProfile.ciaAssessment.confidentiality': 'Vertraulichkeit',
      'riskProfile.ciaAssessment.integrity': 'Integrität',
      'riskProfile.ciaAssessment.availability': 'Verfügbarkeit',
    }

    return labels[fieldPath] || fieldPath
  }

  /**
   * Format a value for display
   */
  private formatValue(value: unknown): string {
    if (value === true) return 'Ja'
    if (value === false) return 'Nein'
    if (value === null || value === undefined) return 'nicht gesetzt'
    if (Array.isArray(value)) return value.join(', ')
    return String(value)
  }

  /**
   * Derive all TOMs based on the current context
   */
  deriveAllTOMs(context: RulesEngineEvaluationContext): DerivedTOM[] {
    const results = this.evaluateControls(context)

    return results.map((result) => {
      const control = getControlById(result.controlId)
      if (!control) {
        throw new Error(`Control not found: ${result.controlId}`)
      }

      return {
        id: `derived-${result.controlId}`,
        controlId: result.controlId,
        name: control.name.de,
        description: control.description.de,
        applicability: result.applicability,
        applicabilityReason: result.reason,
        implementationStatus: 'NOT_IMPLEMENTED',
        responsiblePerson: null,
        responsibleDepartment: null,
        implementationDate: null,
        reviewDate: null,
        linkedEvidence: [],
        evidenceGaps: [...control.evidenceRequirements],
        aiGeneratedDescription: null,
        aiRecommendations: [],
      }
    })
  }

  /**
   * Get only required and recommended TOMs
   */
  getApplicableTOMs(context: RulesEngineEvaluationContext): DerivedTOM[] {
    const allTOMs = this.deriveAllTOMs(context)
    return allTOMs.filter(
      (tom) =>
        tom.applicability === 'REQUIRED' || tom.applicability === 'RECOMMENDED'
    )
  }

  /**
   * Get only required TOMs
   */
  getRequiredTOMs(context: RulesEngineEvaluationContext): DerivedTOM[] {
    const allTOMs = this.deriveAllTOMs(context)
    return allTOMs.filter((tom) => tom.applicability === 'REQUIRED')
  }

  /**
   * Perform gap analysis on derived TOMs and evidence
   */
  performGapAnalysis(
    derivedTOMs: DerivedTOM[],
    documents: EvidenceDocument[]
  ): GapAnalysisResult {
    const missingControls: MissingControl[] = []
    const partialControls: PartialControl[] = []
    const missingEvidence: MissingEvidence[] = []
    const recommendations: string[] = []

    let totalScore = 0
    let totalWeight = 0

    // Analyze each required/recommended TOM
    const applicableTOMs = derivedTOMs.filter(
      (tom) =>
        tom.applicability === 'REQUIRED' || tom.applicability === 'RECOMMENDED'
    )

    for (const tom of applicableTOMs) {
      const control = getControlById(tom.controlId)
      if (!control) continue

      const weight = tom.applicability === 'REQUIRED' ? 3 : 1
      totalWeight += weight

      // Check implementation status
      if (tom.implementationStatus === 'NOT_IMPLEMENTED') {
        missingControls.push({
          controlId: tom.controlId,
          reason: `${control.name.de} ist nicht implementiert`,
          priority: control.priority,
        })
        // Score: 0 for not implemented
      } else if (tom.implementationStatus === 'PARTIAL') {
        partialControls.push({
          controlId: tom.controlId,
          missingAspects: tom.evidenceGaps,
        })
        // Score: 50% for partial
        totalScore += weight * 0.5
      } else {
        // Fully implemented
        totalScore += weight
      }

      // Check evidence
      const linkedEvidenceIds = tom.linkedEvidence
      const requiredEvidence = control.evidenceRequirements
      const providedEvidence = documents.filter((doc) =>
        linkedEvidenceIds.includes(doc.id)
      )

      if (providedEvidence.length < requiredEvidence.length) {
        const missing = requiredEvidence.filter(
          (req) =>
            !providedEvidence.some(
              (doc) =>
                doc.documentType === 'POLICY' ||
                doc.documentType === 'CERTIFICATE' ||
                doc.originalName.toLowerCase().includes(req.toLowerCase())
            )
        )

        if (missing.length > 0) {
          missingEvidence.push({
            controlId: tom.controlId,
            requiredEvidence: missing,
          })
        }
      }
    }

    // Calculate overall score as percentage
    const overallScore =
      totalWeight > 0 ? Math.round((totalScore / totalWeight) * 100) : 0

    // Generate recommendations
    if (missingControls.length > 0) {
      const criticalMissing = missingControls.filter(
        (mc) => mc.priority === 'CRITICAL'
      )
      if (criticalMissing.length > 0) {
        recommendations.push(
          `${criticalMissing.length} kritische Kontrollen sind nicht implementiert. Diese sollten priorisiert werden.`
        )
      }
    }

    if (partialControls.length > 0) {
      recommendations.push(
        `${partialControls.length} Kontrollen sind nur teilweise implementiert. Vervollständigen Sie die Implementierung.`
      )
    }

    if (missingEvidence.length > 0) {
      recommendations.push(
        `Für ${missingEvidence.length} Kontrollen fehlen Nachweisdokumente. Laden Sie die entsprechenden Dokumente hoch.`
      )
    }

    if (overallScore >= 80) {
      recommendations.push(
        'Ihr TOM-Compliance-Score ist gut. Führen Sie regelmäßige Überprüfungen durch.'
      )
    } else if (overallScore >= 50) {
      recommendations.push(
        'Ihr TOM-Compliance-Score erfordert Verbesserungen. Fokussieren Sie sich auf die kritischen Lücken.'
      )
    } else {
      recommendations.push(
        'Ihr TOM-Compliance-Score ist niedrig. Eine systematische Überarbeitung der Maßnahmen wird empfohlen.'
      )
    }

    return {
      overallScore,
      missingControls,
      partialControls,
      missingEvidence,
      recommendations,
      generatedAt: new Date(),
    }
  }

  /**
   * Get controls by applicability level
   */
  getControlsByApplicability(
    context: RulesEngineEvaluationContext,
    applicability: ControlApplicability
  ): ControlLibraryEntry[] {
    const results = this.evaluateControls(context)
    return results
      .filter((r) => r.applicability === applicability)
      .map((r) => getControlById(r.controlId))
      .filter((c): c is ControlLibraryEntry => c !== undefined)
  }

  /**
   * Get summary statistics for the evaluation
   */
  getSummaryStatistics(context: RulesEngineEvaluationContext): {
    total: number
    required: number
    recommended: number
    optional: number
    notApplicable: number
    byCategory: Map<string, { required: number; recommended: number }>
  } {
    const results = this.evaluateControls(context)

    const stats = {
      total: results.length,
      required: 0,
      recommended: 0,
      optional: 0,
      notApplicable: 0,
      byCategory: new Map<string, { required: number; recommended: number }>(),
    }

    for (const result of results) {
      switch (result.applicability) {
        case 'REQUIRED':
          stats.required++
          break
        case 'RECOMMENDED':
          stats.recommended++
          break
        case 'OPTIONAL':
          stats.optional++
          break
        case 'NOT_APPLICABLE':
          stats.notApplicable++
          break
      }

      // Count by category
      const control = getControlById(result.controlId)
      if (control) {
        const category = control.category
        const existing = stats.byCategory.get(category) || {
          required: 0,
          recommended: 0,
        }

        if (result.applicability === 'REQUIRED') {
          existing.required++
        } else if (result.applicability === 'RECOMMENDED') {
          existing.recommended++
        }

        stats.byCategory.set(category, existing)
      }
    }

    return stats
  }

  /**
   * Check if a specific control is applicable
   */
  isControlApplicable(
    controlId: string,
    context: RulesEngineEvaluationContext
  ): boolean {
    const control = getControlById(controlId)
    if (!control) return false

    const result = this.evaluateControl(control, context)
    return (
      result.applicability === 'REQUIRED' ||
      result.applicability === 'RECOMMENDED'
    )
  }

  /**
   * Get all controls that match a specific tag
   */
  getControlsByTagWithApplicability(
    tag: string,
    context: RulesEngineEvaluationContext
  ): Array<{ control: ControlLibraryEntry; result: RulesEngineResult }> {
    return this.controls
      .filter((control) => control.tags.includes(tag))
      .map((control) => ({
        control,
        result: this.evaluateControl(control, context),
      }))
  }

  /**
   * Reload controls (useful if the control library is updated)
   */
  reloadControls(): void {
    this.controls = getAllControls()
  }
}

// =============================================================================
// SINGLETON INSTANCE
// =============================================================================

let rulesEngineInstance: TOMRulesEngine | null = null

export function getTOMRulesEngine(): TOMRulesEngine {
  if (!rulesEngineInstance) {
    rulesEngineInstance = new TOMRulesEngine()
  }
  return rulesEngineInstance
}

// =============================================================================
// HELPER FUNCTIONS
// =============================================================================

/**
 * Quick evaluation of controls for a context
 */
export function evaluateControlsForContext(
  context: RulesEngineEvaluationContext
): RulesEngineResult[] {
  return getTOMRulesEngine().evaluateControls(context)
}

/**
 * Quick derivation of TOMs for a context
 */
export function deriveTOMsForContext(
  context: RulesEngineEvaluationContext
): DerivedTOM[] {
  return getTOMRulesEngine().deriveAllTOMs(context)
}

/**
 * Quick gap analysis
 */
export function performQuickGapAnalysis(
  derivedTOMs: DerivedTOM[],
  documents: EvidenceDocument[]
): GapAnalysisResult {
  return getTOMRulesEngine().performGapAnalysis(derivedTOMs, documents)
}
