## nimpak/security/periodic_scanner.nim
## Periodic integrity scanning system for NimPak
##
## This module implements configurable periodic integrity scans with
## scheduling, incremental scanning, and integration with the health check framework.

import std/[os, times, json, asyncdispatch, strutils, strformat, tables, random, sequtils]
import integrity_monitor, hash_verifier, event_logger
import ../cli/core

type
  ScanSchedule* = object
    enabled*: bool
    fullScanInterval*: int      # Hours between full scans
    incrementalInterval*: int   # Minutes between incremental scans
    fullScanHour*: int         # Hour of day for full scan (0-23)
    maxConcurrentScans*: int   # Maximum concurrent scan operations
    scanTimeout*: int          # Timeout for individual scans (seconds)

  ScanResult* = object
    scanId*: string
    scanType*: string          # "full", "incremental", "targeted"
    startTime*: times.DateTime
    endTime*: times.DateTime
    duration*: float
    packagesScanned*: int
    issuesFound*: int
    results*: seq[IntegrityCheckResult]
    success*: bool

  PeriodicScanner* = object
    schedule*: ScanSchedule
    monitor*: IntegrityMonitor
    lastFullScan*: times.DateTime
    lastIncrementalScan*: times.DateTime
    scanHistory*: seq[ScanResult]
    isRunning*: bool
    currentScanId*: string

# =============================================================================
# Scanner Configuration
# =============================================================================

proc getDefaultScanSchedule*(): ScanSchedule =
  ## Get default periodic scan schedule
  ScanSchedule(
    enabled: true,
    fullScanInterval: 24,       # Daily full scans
    incrementalInterval: 15,    # Every 15 minutes
    fullScanHour: 2,           # 2 AM for full scans
    maxConcurrentScans: 2,
    scanTimeout: 3600          # 1 hour timeout
  )

proc newPeriodicScanner*(schedule: ScanSchedule, monitor: IntegrityMonitor): PeriodicScanner =
  ## Create a new periodic scanner
  PeriodicScanner(
    schedule: schedule,
    monitor: monitor,
    lastFullScan: default(times.DateTime),
    lastIncrementalScan: default(times.DateTime),
    scanHistory: @[],
    isRunning: false,
    currentScanId: ""
  )

# =============================================================================
# Scan Execution
# =============================================================================

proc generateScanId*(scanType: string): string =
  ## Generate unique scan ID
  let timestamp = $getTime().toUnix()
  return fmt"scan_{scanType}_{timestamp}_{rand(1000..9999)}"

proc executeFullScan*(scanner: var PeriodicScanner): Future[ScanResult] {.async.} =
  ## Execute a full system integrity scan
  let scanId = generateScanId("full")
  let startTime = now()

  var result = ScanResult(
    scanId: scanId,
    scanType: "full",
    startTime: startTime,
    endTime: startTime,
    duration: 0.0,
    packagesScanned: 0,
    issuesFound: 0,
    results: @[],
    success: false
  )

  try:
    logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "periodic-scanner",
      fmt"Starting full integrity scan: {scanId}")

    scanner.currentScanId = scanId

    # Execute comprehensive verification
    result.results = verifyAllPackages(scanner.monitor)
    result.packagesScanned = result.results.len
    result.issuesFound = result.results.countIt(not it.success)

    result.endTime = now()
    result.duration = (result.endTime - result.startTime).inMilliseconds.float / 1000.0
    result.success = result.issuesFound == 0

    scanner.lastFullScan = result.endTime
    scanner.scanHistory.add(result)

    # Log completion
    let severity = if result.success: SeverityInfo else: SeverityWarning
    logGlobalSecurityEvent(EventSystemHealthCheck, severity, "periodic-scanner",
      fmt"Full scan completed: {scanId} - {result.packagesScanned} packages, {result.issuesFound} issues")

  except Exception as e:
    result.endTime = now()
    result.duration = (result.endTime - result.startTime).inMilliseconds.float / 1000.0
    result.success = false

    logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "periodic-scanner",
      fmt"Full scan failed: {scanId} - {e.msg}")

  finally:
    scanner.currentScanId = ""

  return result

proc executeIncrementalScan*(scanner: var PeriodicScanner): Future[ScanResult] {.async.} =
  ## Execute an incremental integrity scan (recently modified packages only)
  let scanId = generateScanId("incremental")
  let startTime = now()

  var result = ScanResult(
    scanId: scanId,
    scanType: "incremental",
    startTime: startTime,
    endTime: startTime,
    duration: 0.0,
    packagesScanned: 0,
    issuesFound: 0,
    results: @[],
    success: false
  )

  try:
    logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "periodic-scanner",
      fmt"Starting incremental integrity scan: {scanId}")

    scanner.currentScanId = scanId

    # Find recently modified packages (last 15 minutes)
    let recentPackages = findRecentlyModifiedPackages(scanner.monitor, scanner.schedule.incrementalInterval)

    if recentPackages.len > 0:
      # Verify only recently modified packages
      for packagePath in recentPackages:
        let packageName = extractPackageName(packagePath)
        let checkResult = verifyPackageIntegrity(packageName, packagePath)
        result.results.add(checkResult)

        if not checkResult.success:
          result.issuesFound += 1

      result.packagesScanned = recentPackages.len
    else:
      # No recent modifications - scan is successful but empty
      result.packagesScanned = 0

    result.endTime = now()
    result.duration = (result.endTime - result.startTime).inMilliseconds.float / 1000.0
    result.success = result.issuesFound == 0

    scanner.lastIncrementalScan = result.endTime
    scanner.scanHistory.add(result)

    # Log completion
    let severity = if result.success: SeverityInfo else: SeverityWarning
    logGlobalSecurityEvent(EventSystemHealthCheck, severity, "periodic-scanner",
      fmt"Incremental scan completed: {scanId} - {result.packagesScanned} packages, {result.issuesFound} issues")

  except Exception as e:
    result.endTime = now()
    result.duration = (result.endTime - result.startTime).inMilliseconds.float / 1000.0
    result.success = false

    logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "periodic-scanner",
      fmt"Incremental scan failed: {scanId} - {e.msg}")

  finally:
    scanner.currentScanId = ""

  return result

proc executeTargetedScan*(scanner: var PeriodicScanner, packageNames: seq[string]): Future[ScanResult] {.async.} =
  ## Execute a targeted integrity scan for specific packages
  let scanId = generateScanId("targeted")
  let startTime = now()

  var result = ScanResult(
    scanId: scanId,
    scanType: "targeted",
    startTime: startTime,
    endTime: startTime,
    duration: 0.0,
    packagesScanned: 0,
    issuesFound: 0,
    results: @[],
    success: false
  )

  try:
    logGlobalSecurityEvent(EventSystemHealthCheck, SeverityInfo, "periodic-scanner",
      fmt"Starting targeted integrity scan: {scanId} - {packageNames.len} packages")

    scanner.currentScanId = scanId

    # Verify specified packages
    for packageName in packageNames:
      let packagePath = fmt"/Programs/{packageName}/current/{packageName}.npk"
      if fileExists(packagePath):
        let checkResult = verifyPackageIntegrity(packageName, packagePath)
        result.results.add(checkResult)

        if not checkResult.success:
          result.issuesFound += 1

        result.packagesScanned += 1
      else:
        # Package not found
        let notFoundResult = IntegrityCheckResult(
          checkType: CheckFileIntegrity,
          packageName: packageName,
          success: false,
          message: fmt"Package not found: {packageName}",
          details: %*{"package_path": packagePath},
          checkTime: now(),
          duration: 0.0
        )
        result.results.add(notFoundResult)
        result.issuesFound += 1

    result.endTime = now()
    result.duration = (result.endTime - result.startTime).inMilliseconds.float / 1000.0
    result.success = result.issuesFound == 0

    scanner.scanHistory.add(result)

    # Log completion
    let severity = if result.success: SeverityInfo else: SeverityWarning
    logGlobalSecurityEvent(EventSystemHealthCheck, severity, "periodic-scanner",
      fmt"Targeted scan completed: {scanId} - {result.packagesScanned} packages, {result.issuesFound} issues")

  except Exception as e:
    result.endTime = now()
    result.duration = (result.endTime - result.startTime).inMilliseconds.float / 1000.0
    result.success = false

    logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "periodic-scanner",
      fmt"Targeted scan failed: {scanId} - {e.msg}")

  finally:
    scanner.currentScanId = ""

  return result

# =============================================================================
# Scan Scheduling and Management
# =============================================================================

proc shouldRunFullScan*(scanner: PeriodicScanner): bool =
  ## Check if a full scan should be run based on schedule
  if not scanner.schedule.enabled:
    return false

  let currentTime = now()
  let currentHour = currentTime.hour

  # Check if it's the scheduled hour for full scan
  if currentHour != scanner.schedule.fullScanHour:
    return false

  # Check if enough time has passed since last full scan
  if scanner.lastFullScan != default(times.DateTime):
    let hoursSinceLastScan = (currentTime - scanner.lastFullScan).inHours
    if hoursSinceLastScan < scanner.schedule.fullScanInterval:
      return false

  return true

proc shouldRunIncrementalScan*(scanner: PeriodicScanner): bool =
  ## Check if an incremental scan should be run based on schedule
  if not scanner.schedule.enabled:
    return false

  let currentTime = now()

  # Check if enough time has passed since last incremental scan
  if scanner.lastIncrementalScan != default(times.DateTime):
    let minutesSinceLastScan = (currentTime - scanner.lastIncrementalScan).inMinutes
    if minutesSinceLastScan < scanner.schedule.incrementalInterval:
      return false

  return true

proc startPeriodicScanning*(scanner: var PeriodicScanner) {.async.} =
  ## Start the periodic scanning daemon
  if scanner.isRunning:
    return

  scanner.isRunning = true

  logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "periodic-scanner",
    fmt"Starting periodic scanner - full: {scanner.schedule.fullScanInterval}h, incremental: {scanner.schedule.incrementalInterval}m")

  while scanner.isRunning and scanner.schedule.enabled:
    try:
      # Check if full scan should run
      if scanner.shouldRunFullScan():
        showInfo("🔍 Starting scheduled full integrity scan...")
        let result = await scanner.executeFullScan()

        if result.success:
          showSuccess(fmt"Full scan completed: {result.packagesScanned} packages verified")
        else:
          showWarning(fmt"Full scan found issues: {result.issuesFound} problems detected")

      # Check if incremental scan should run
      elif scanner.shouldRunIncrementalScan():
        let result = await scanner.executeIncrementalScan()

        if result.packagesScanned > 0:
          if result.success:
            showInfo(fmt"Incremental scan completed: {result.packagesScanned} packages verified")
          else:
            showWarning(fmt"Incremental scan found issues: {result.issuesFound} problems detected")

      # Sleep for 1 minute before next check
      await sleepAsync(60000)

    except Exception as e:
      logGlobalSecurityEvent(EventSecurityIncident, SeverityError, "periodic-scanner",
        fmt"Periodic scanner error: {e.msg}")
      await sleepAsync(300000)  # Wait 5 minutes on error

  logGlobalSecurityEvent(EventSystemStartup, SeverityInfo, "periodic-scanner",
    "Periodic scanner stopped")

proc stopPeriodicScanning*(scanner: var PeriodicScanner) =
  ## Stop the periodic scanning daemon
  scanner.isRunning = false

# =============================================================================
# Scan History and Reporting
# =============================================================================

proc getScanHistory*(scanner: PeriodicScanner, limit: int = 10): seq[ScanResult] =
  ## Get recent scan history
  let history = scanner.scanHistory
  if history.len <= limit:
    return history
  else:
    return history[^limit..^1]  # Return last 'limit' scans

proc getScanStatistics*(scanner: PeriodicScanner): JsonNode =
  ## Get scan statistics
  let history = scanner.scanHistory
  let recentHistory = scanner.getScanHistory(30)  # Last 30 scans

  var totalScans = history.len
  var successfulScans = history.countIt(it.success)
  var totalPackagesScanned = 0
  var totalIssuesFound = 0
  var totalDuration = 0.0

  for scan in history:
    totalPackagesScanned += scan.packagesScanned
    totalIssuesFound += scan.issuesFound
    totalDuration += scan.duration

  var scanTypeCounts = initTable[string, int]()
  for scan in recentHistory:
    if scan.scanType in scanTypeCounts:
      scanTypeCounts[scan.scanType] += 1
    else:
      scanTypeCounts[scan.scanType] = 1

  return %*{
    "total_scans": totalScans,
    "successful_scans": successfulScans,
    "failed_scans": totalScans - successfulScans,
    "success_rate": if totalScans > 0: (successfulScans.float / totalScans.float) else: 0.0,
    "total_packages_scanned": totalPackagesScanned,
    "total_issues_found": totalIssuesFound,
    "total_duration": totalDuration,
    "average_duration": if totalScans > 0: (totalDuration / totalScans.float) else: 0.0,
    "last_full_scan": if scanner.lastFullScan != default(times.DateTime): $scanner.lastFullScan else: "never",
    "last_incremental_scan": if scanner.lastIncrementalScan != default(times.DateTime): $scanner.lastIncrementalScan else: "never",
    "scan_type_counts": %scanTypeCounts,
    "currently_running": scanner.isRunning,
    "current_scan_id": scanner.currentScanId
  }

proc generateScanReport*(scanner: PeriodicScanner, scanId: string): JsonNode =
  ## Generate detailed report for a specific scan
  for scan in scanner.scanHistory:
    if scan.scanId == scanId:
      return %*{
        "scan_id": scan.scanId,
        "scan_type": scan.scanType,
        "start_time": $scan.startTime,
        "end_time": $scan.endTime,
        "duration": scan.duration,
        "packages_scanned": scan.packagesScanned,
        "issues_found": scan.issuesFound,
        "success": scan.success,
        "results": scan.results.mapIt(%*{
          "package_name": it.packageName,
          "check_type": $it.checkType,
          "success": it.success,
          "message": it.message,
          "duration": it.duration,
          "timestamp": $it.checkTime
        })
      }

  return %*{"error": fmt"Scan not found: {scanId}"}

# =============================================================================
# CLI Integration
# =============================================================================

proc nipScanCommand*(args: seq[string]): CommandResult =
  ## Implement nip scan command for manual scan execution
  try:
    if args.len == 0:
      return errorResult("Usage: nip scan <full|incremental|targeted> [options]")

    let scanType = args[0].toLower()
    let config = getDefaultIntegrityConfig()
    let schedule = getDefaultScanSchedule()
    let monitor = newIntegrityMonitor(config)
    var scanner = newPeriodicScanner(schedule, monitor)

    case scanType:
    of "full":
      showInfo("🔍 Starting manual full integrity scan...")
      let result = waitFor scanner.executeFullScan()

      if result.success:
        return successResult(fmt"Full scan completed: {result.packagesScanned} packages verified, no issues found")
      else:
        return errorResult(fmt"Full scan found issues: {result.issuesFound} problems detected in {result.packagesScanned} packages", 1)

    of "incremental":
      showInfo("🔍 Starting manual incremental integrity scan...")
      let result = waitFor scanner.executeIncrementalScan()

      if result.packagesScanned == 0:
        return successResult("Incremental scan completed: no recently modified packages found")
      elif result.success:
        return successResult(fmt"Incremental scan completed: {result.packagesScanned} packages verified, no issues found")
      else:
        return errorResult(fmt"Incremental scan found issues: {result.issuesFound} problems detected in {result.packagesScanned} packages", 1)

    of "targeted":
      if args.len < 2:
        return errorResult("Usage: nip scan targeted <package1> [package2] ...")

      let packageNames = args[1..^1]
      showInfo(fmt"🔍 Starting targeted integrity scan for {packageNames.len} packages...")
      let result = waitFor scanner.executeTargetedScan(packageNames)

      if result.success:
        return successResult(fmt"Targeted scan completed: {result.packagesScanned} packages verified, no issues found")
      else:
        return errorResult(fmt"Targeted scan found issues: {result.issuesFound} problems detected in {result.packagesScanned} packages", 1)

    of "status":
      let stats = scanner.getScanStatistics()
      outputData(stats, "Scan Statistics")
      return successResult("Scan status retrieved")

    of "history":
      let limit = if args.len > 1: parseInt(args[1]) else: 10
      let history = scanner.getScanHistory(limit)
      let historyData = %*{
        "scan_history": history.mapIt(%*{
          "scan_id": it.scanId,
          "scan_type": it.scanType,
          "start_time": $it.startTime,
          "duration": it.duration,
          "packages_scanned": it.packagesScanned,
          "issues_found": it.issuesFound,
          "success": it.success
        })
      }
      outputData(historyData, fmt"Last {limit} Scans")
      return successResult(fmt"Scan history retrieved ({history.len} scans)")

    else:
      return errorResult(fmt"Unknown scan type: {scanType}. Use: full, incremental, targeted, status, or history")

  except Exception as e:
    return errorResult(fmt"Scan command failed: {e.msg}")

# =============================================================================
# Export main functions
# =============================================================================

export ScanSchedule, ScanResult, PeriodicScanner
export getDefaultScanSchedule, newPeriodicScanner
export executeFullScan, executeIncrementalScan, executeTargetedScan
export shouldRunFullScan, shouldRunIncrementalScan
export startPeriodicScanning, stopPeriodicScanning
export getScanHistory, getScanStatistics, generateScanReport
export nipScanCommand