## Integration tests for the bootstrap system
## Tests end-to-end bootstrap installation flow

import std/[os, osproc, strutils, json, times]
import ../src/nimpak/build/recipe_manager
import ../src/nimpak/build/download_manager
import ../src/nimpak/build/installation_manager
import ../src/nimpak/build/recipe_parser

const
  TestDir = "/tmp/nip-bootstrap-test"
  RecipeRepoUrl = "https://git.maiwald.work/Nexus/nip-recipes.git"

type
  TestResult = object
    name: string
    passed: bool
    duration: float
    error: string

var testResults: seq[TestResult]

proc startTest(name: string): float =
  echo "\n🧪 Testing: ", name
  result = cpuTime()

proc endTest(name: string, startTime: float, passed: bool, error: string = "") =
  let duration = cpuTime() - startTime
  testResults.add(TestResult(
    name: name,
    passed: passed,
    duration: duration,
    error: error
  ))

  if passed:
    echo "✅ PASSED (", duration.formatFloat(ffDecimal, 3), "s)"
  else:
    echo "❌ FAILED (", duration.formatFloat(ffDecimal, 3), "s)"
    if error.len > 0:
      echo "   Error: ", error

proc setupTestEnvironment() =
  echo "🔧 Setting up test environment..."

  # Clean up any previous test runs
  if dirExists(TestDir):
    removeDir(TestDir)

  createDir(TestDir)
  createDir(TestDir / "bootstrap")
  createDir(TestDir / "cache")
  createDir(TestDir / "recipes")

  echo "✅ Test environment ready: ", TestDir

proc cleanupTestEnvironment() =
  echo "\n🧹 Cleaning up test environment..."
  if dirExists(TestDir):
    removeDir(TestDir)
  echo "✅ Cleanup complete"

proc testRecipeManagerInit(): bool =
  let start = startTest("RecipeManager initialization")

  try:
    let manager = newRecipeManager(TestDir / "recipes")
    result = true
    endTest("RecipeManager initialization", start, true)
  except Exception as e:
    endTest("RecipeManager initialization", start, false, e.msg)
    result = false

proc testRecipeLoading(): bool =
  let start = startTest("Recipe loading and parsing")

  try:
    let manager = newRecipeManager(TestDir / "recipes")

    # Try to load a recipe (will fail if repo not cloned, which is expected)
    # This tests the error handling
    try:
      discard manager.loadRecipe("nix")
      # If we get here, recipe loaded successfully
      result = true
    except RecipeError:
      # Expected if recipes not available
      echo "   Note: Recipe not available (expected in test environment)"
      result = true

    endTest("Recipe loading and parsing", start, result)
  except Exception as e:
    endTest("Recipe loading and parsing", start, false, e.msg)
    result = false

proc testDownloadManagerInit(): bool =
  let start = startTest("DownloadManager initialization")

  try:
    let manager = newDownloadManager(TestDir / "cache")
    result = true
    endTest("DownloadManager initialization", start, true)
  except Exception as e:
    endTest("DownloadManager initialization", start, false, e.msg)
    result = false

proc testChecksumVerification(): bool =
  let start = startTest("Checksum verification")

  try:
    # Create a test file with known content
    let testFile = TestDir / "test.txt"
    writeFile(testFile, "Hello, NIP Bootstrap!")

    let manager = newDownloadManager(TestDir / "cache")

    # Calculate checksum
    let checksum = manager.calculateChecksum(testFile)

    # Verify it's in the correct format (blake2b-512 multihash)
    if checksum.startsWith("blake2b-"):
      result = true
    else:
      result = false
      endTest("Checksum verification", start, false, "Invalid checksum format: " & checksum)
      return

    # Verify the checksum
    let verified = manager.verifyChecksum(testFile, checksum)
    result = verified

    if verified:
      endTest("Checksum verification", start, true)
    else:
      endTest("Checksum verification", start, false, "Checksum verification failed")
  except Exception as e:
    endTest("Checksum verification", start, false, e.msg)
    result = false

proc testInstallationManagerInit(): bool =
  let start = startTest("InstallationManager initialization")

  try:
    let manager = newInstallationManager(TestDir / "bootstrap")
    result = true
    endTest("InstallationManager initialization", start, true)
  except Exception as e:
    endTest("InstallationManager initialization", start, false, e.msg)
    result = false

proc testArchiveExtraction(): bool =
  let start = startTest("Archive extraction")

  try:
    # Create a test tar.gz archive
    let testDir = TestDir / "archive-test"
    let extractDir = TestDir / "extracted"
    createDir(testDir)

    writeFile(testDir / "test.txt", "Test content")
    writeFile(testDir / "test2.txt", "More content")

    # Create tar.gz
    let archivePath = TestDir / "test.tar.gz"
    let cmd = "tar -czf " & archivePath & " -C " & testDir & " ."
    let (output, exitCode) = execCmdEx(cmd)

    if exitCode != 0:
      endTest("Archive extraction", start, false, "Failed to create test archive")
      return false

    # Test extraction
    let manager = newInstallationManager(TestDir / "bootstrap")
    manager.extractArchive(archivePath, extractDir)

    # Verify files were extracted
    result = fileExists(extractDir / "test.txt") and
             fileExists(extractDir / "test2.txt")

    if result:
      endTest("Archive extraction", start, true)
    else:
      endTest("Archive extraction", start, false, "Extracted files not found")
  except Exception as e:
    endTest("Archive extraction", start, false, e.msg)
    result = false

proc testScriptExecution(): bool =
  let start = startTest("Script execution")

  try:
    # Create a test script
    let scriptPath = TestDir / "test-script.sh"
    writeFile(scriptPath, """#!/bin/bash
echo "Test script executed"
exit 0
""")

    # Make executable
    setFilePermissions(scriptPath, {fpUserExec, fpUserRead, fpUserWrite})

    # Execute script
    let manager = newInstallationManager(TestDir / "bootstrap")
    let (output, exitCode) = manager.executeScript(scriptPath, TestDir)

    result = exitCode == 0 and "Test script executed" in output

    if result:
      endTest("Script execution", start, true)
    else:
      endTest("Script execution", start, false, "Script execution failed or wrong output")
  except Exception as e:
    endTest("Script execution", start, false, e.msg)
    result = false

proc testRecipeValidation(): bool =
  let start = startTest("Recipe validation")

  try:
    # Create a test recipe
    let recipePath = TestDir / "test-recipe.kdl"
    writeFile(recipePath, """
recipe "test-tool" {
  version "1.0.0"
  description "Test tool for integration testing"

  platform "linux-x86_64" {
    url "https://example.com/test-tool.tar.gz"
    checksum "blake2b-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"

    install-script "install.sh"
    verify-script "verify.sh"
  }
}
""")

    # Parse and validate
    let recipe = parseRecipe(recipePath)

    result = recipe.name == "test-tool" and
             recipe.version == "1.0.0" and
             recipe.platforms.len > 0

    if result:
      endTest("Recipe validation", start, true)
    else:
      endTest("Recipe validation", start, false, "Recipe validation failed")
  except Exception as e:
    endTest("Recipe validation", start, false, e.msg)
    result = false

proc testPlatformSelection(): bool =
  let start = startTest("Platform selection")

  try:
    # Create a recipe with multiple platforms
    let recipePath = TestDir / "multi-platform.kdl"
    writeFile(recipePath, """
recipe "multi-tool" {
  version "1.0.0"
  description "Multi-platform test tool"

  platform "linux-x86_64" {
    url "https://example.com/linux-x86_64.tar.gz"
    checksum "blake2b-1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111"
  }

  platform "linux-aarch64" {
    url "https://example.com/linux-aarch64.tar.gz"
    checksum "blake2b-2222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222"
  }
}
""")

    let recipe = parseRecipe(recipePath)

    # Test platform selection
    when defined(amd64) or defined(x86_64):
      let platform = recipe.selectPlatform()
      result = platform.isSome and "x86_64" in platform.get().name
    else:
      # On other architectures, just check that selection works
      result = true

    if result:
      endTest("Platform selection", start, true)
    else:
      endTest("Platform selection", start, false, "Platform selection failed")
  except Exception as e:
    endTest("Platform selection", start, false, e.msg)
    result = false

proc testErrorHandling(): bool =
  let start = startTest("Error handling")

  try:
    var allPassed = true

    # Test 1: Invalid recipe file
    try:
      let invalidRecipe = TestDir / "invalid.kdl"
      writeFile(invalidRecipe, "this is not valid KDL {{{")
      discard parseRecipe(invalidRecipe)
      allPassed = false  # Should have thrown
    except RecipeError:
      discard  # Expected

    # Test 2: Missing file
    try:
      discard parseRecipe(TestDir / "nonexistent.kdl")
      allPassed = false  # Should have thrown
    except IOError, OSError:
      discard  # Expected

    # Test 3: Invalid checksum
    let manager = newDownloadManager(TestDir / "cache")
    let testFile = TestDir / "test-checksum.txt"
    writeFile(testFile, "content")

    let verified = manager.verifyChecksum(testFile, "blake2b-0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
    if verified:
      allPassed = false  # Should have failed

    result = allPassed

    if result:
      endTest("Error handling", start, true)
    else:
      endTest("Error handling", start, false, "Error handling not working correctly")
  except Exception as e:
    endTest("Error handling", start, false, e.msg)
    result = false

proc testCaching(): bool =
  let start = startTest("Download caching")

  try:
    let manager = newDownloadManager(TestDir / "cache")

    # Create a "downloaded" file in cache
    let cacheDir = TestDir / "cache"
    let testUrl = "https://example.com/test-file.tar.gz"
    let cachedFile = manager.getCachePath(testUrl)

    createDir(cachedFile.parentDir())
    writeFile(cachedFile, "cached content")

    # Check if cache hit works
    let isCached = manager.isCached(testUrl)

    result = isCached

    if result:
      endTest("Download caching", start, true)
    else:
      endTest("Download caching", start, false, "Cache detection failed")
  except Exception as e:
    endTest("Download caching", start, false, e.msg)
    result = false

proc printTestSummary() =
  echo "\n" & "=".repeat(60)
  echo "TEST SUMMARY"
  echo "=".repeat(60)

  var passed = 0
  var failed = 0
  var totalDuration = 0.0

  for result in testResults:
    totalDuration += result.duration
    if result.passed:
      inc passed
      echo "✅ ", result.name
    else:
      inc failed
      echo "❌ ", result.name
      if result.error.len > 0:
        echo "   ", result.error

  echo ""
  echo "Total: ", testResults.len, " tests"
  echo "Passed: ", passed, " (", (passed * 100 div testResults.len), "%)"
  echo "Failed: ", failed
  echo "Duration: ", totalDuration.formatFloat(ffDecimal, 3), "s"
  echo "=".repeat(60)

  if failed > 0:
    quit(1)

proc main() =
  echo "NIP Bootstrap Integration Tests"
  echo "================================\n"

  setupTestEnvironment()

  # Run all tests
  discard testRecipeManagerInit()
  discard testRecipeLoading()
  discard testDownloadManagerInit()
  discard testChecksumVerification()
  discard testInstallationManagerInit()
  discard testArchiveExtraction()
  discard testScriptExecution()
  discard testRecipeValidation()
  discard testPlatformSelection()
  discard testErrorHandling()
  discard testCaching()

  cleanupTestEnvironment()
  printTestSummary()

when isMainModule:
  main()
