# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.

## NimPak Migration Tools
##
## Tools for migrating from legacy formats and other package managers.
## Task 42: Implement migration tools.

import std/[os, strutils, strformat, json, tables, sequtils, times]
import ./types
import cas
import logging

type
  MigrationSource* = enum
    OldNip,       ## Legacy NIP format
    Flatpak,      ## Flatpak applications
    Snap,         ## Snap packages
    AppImage,     ## AppImage files
    Docker,       ## Docker images
    Nix,          ## Nix packages
    Pkgsrc        ## pkgsrc packages

  MigrationResult* = object
    success*: bool
    source*: MigrationSource
    packageName*: string
    targetPath*: string
    casHashes*: seq[string]
    errors*: seq[string]
    warnings*: seq[string]

  MigrationStats* = object
    totalFiles*: int
    totalBytes*: int64
    dedupBytes*: int64
    casObjects*: int
    startTime*: DateTime
    endTime*: DateTime

  MigrationManager* = object
    casManager*: CasManager
    logger*: Logger
    dryRun*: bool
    verbose*: bool

# ############################################################################
# Migration Manager Initialization
# ############################################################################

proc initMigrationManager*(casRoot: string, dryRun: bool = false, verbose: bool = false): MigrationManager =
  result = MigrationManager(
    casManager: initCasManager(casRoot, casRoot / "system"),
    logger: initLogger("migration", if verbose: Debug else: Info, {Console}),
    dryRun: dryRun,
    verbose: verbose
  )

# ############################################################################
# Legacy NIP Migration
# ############################################################################

proc migrateLegacyNip*(mm: var MigrationManager, legacyPath: string): MigrationResult =
  ## Migrate from legacy NIP format (pre-unified storage)
  result = MigrationResult(
    success: false,
    source: OldNip,
    packageName: legacyPath.extractFilename,
    casHashes: @[],
    errors: @[],
    warnings: @[]
  )
  
  mm.logger.log(Info, fmt"Migrating legacy NIP: {legacyPath}")
  
  if not dirExists(legacyPath):
    result.errors.add(fmt"Legacy NIP directory not found: {legacyPath}")
    return
  
  # Check for legacy manifest
  let manifestPath = legacyPath / "manifest.kdl"
  let legacyManifest = legacyPath / "package.json"
  
  if not fileExists(manifestPath) and not fileExists(legacyManifest):
    result.errors.add("No manifest found (manifest.kdl or package.json)")
    return
  
  # Enumerate files to migrate
  var stats = MigrationStats(startTime: now())
  
  for file in walkDirRec(legacyPath):
    if file.endswith(".nip-old") or file.contains("/.git/"):
      continue
    
    stats.totalFiles += 1
    let fileInfo = getFileInfo(file)
    stats.totalBytes += fileInfo.size
    
    if not mm.dryRun:
      # Store each file in CAS
      let storeResult = mm.casManager.storeFile(file)
      if storeResult.isOk:
        let obj = storeResult.get()
        result.casHashes.add(obj.hash)
        stats.casObjects += 1
        stats.dedupBytes += obj.size - obj.compressedSize
        mm.logger.log(Debug, fmt"Stored: {file} -> {obj.hash[0..15]}...")
      else:
        result.warnings.add(fmt"Failed to store: {file}")
  
  stats.endTime = now()
  
  # Generate migration report
  mm.logger.log(Info, fmt"Migration stats: {stats.totalFiles} files, {stats.totalBytes} bytes")
  mm.logger.log(Info, fmt"CAS objects: {stats.casObjects}, Dedup savings: {stats.dedupBytes} bytes")
  
  result.success = result.errors.len == 0
  result.targetPath = mm.casManager.rootPath / "migrated" / result.packageName

# ############################################################################
# Flatpak Migration
# ############################################################################

proc migrateFlatpak*(mm: var MigrationManager, appId: string): MigrationResult =
  ## Migrate a Flatpak application to NIP format
  result = MigrationResult(
    success: false,
    source: Flatpak,
    packageName: appId,
    casHashes: @[],
    errors: @[],
    warnings: @[]
  )
  
  mm.logger.log(Info, fmt"Migrating Flatpak app: {appId}")
  
  # Check if flatpak is installed
  let flatpakPath = "/var/lib/flatpak/app" / appId
  let userFlatpakPath = getHomeDir() / ".local/share/flatpak/app" / appId
  
  var sourcePath = ""
  if dirExists(flatpakPath):
    sourcePath = flatpakPath
  elif dirExists(userFlatpakPath):
    sourcePath = userFlatpakPath
  else:
    result.errors.add(fmt"Flatpak app not found: {appId}")
    result.warnings.add("Ensure the app is installed via 'flatpak install'")
    return
  
  # Find current version
  var currentDir = ""
  for dir in walkDir(sourcePath / "current"):
    if dir.kind == pcDir or dir.kind == pcLinkToDir:
      currentDir = dir.path
      break
  
  if currentDir == "":
    result.errors.add("Could not find current version")
    return
  
  # Migrate files
  let filesDir = currentDir / "files"
  if dirExists(filesDir):
    for file in walkDirRec(filesDir):
      if not mm.dryRun:
        let storeResult = mm.casManager.storeFile(file)
        if storeResult.isOk:
          result.casHashes.add(storeResult.get().hash)
  
  # Create NIP manifest from Flatpak metadata
  let metadataPath = currentDir / "metadata"
  if fileExists(metadataPath):
    mm.logger.log(Debug, "Found Flatpak metadata, will convert to NIP manifest")
  
  result.success = result.errors.len == 0
  result.targetPath = getHomeDir() / ".local/share/nexus/nips" / appId

# ############################################################################
# AppImage Migration
# ############################################################################

proc migrateAppImage*(mm: var MigrationManager, appImagePath: string): MigrationResult =
  ## Migrate an AppImage to NIP format
  result = MigrationResult(
    success: false,
    source: AppImage,
    packageName: appImagePath.extractFilename.changeFileExt(""),
    casHashes: @[],
    errors: @[],
    warnings: @[]
  )
  
  mm.logger.log(Info, fmt"Migrating AppImage: {appImagePath}")
  
  if not fileExists(appImagePath):
    result.errors.add(fmt"AppImage not found: {appImagePath}")
    return
  
  # AppImages are squashfs, need to extract
  result.warnings.add("AppImage extraction requires 'unsquashfs' - placeholder")
  
  # Store the AppImage itself as a blob for now
  if not mm.dryRun:
    let storeResult = mm.casManager.storeFile(appImagePath)
    if storeResult.isOk:
      result.casHashes.add(storeResult.get().hash)
      result.success = true

# ############################################################################
# Docker/OCI Migration
# ############################################################################

proc migrateDockerImage*(mm: var MigrationManager, imageName: string): MigrationResult =
  ## Migrate a Docker image to NEXTER format
  result = MigrationResult(
    success: false,
    source: Docker,
    packageName: imageName.replace(":", "-").replace("/", "-"),
    casHashes: @[],
    errors: @[],
    warnings: @[]
  )
  
  mm.logger.log(Info, fmt"Migrating Docker image: {imageName}")
  
  # Export Docker image to tar
  let exportPath = getTempDir() / fmt"docker-export-{result.packageName}.tar"
  
  result.warnings.add("Docker migration requires 'docker save' - placeholder")
  result.warnings.add(fmt"Would export to: {exportPath}")
  
  # TODO: Actually run docker save and process layers
  # Each Docker layer can be stored as a CAS chunk for deduplication
  
  result.success = true  # Placeholder

# ############################################################################
# Nix Package Migration
# ############################################################################

proc migrateNixPackage*(mm: var MigrationManager, nixStorePath: string): MigrationResult =
  ## Migrate a Nix store path to NPK format
  result = MigrationResult(
    success: false,
    source: Nix,
    packageName: nixStorePath.extractFilename.split("-", 1)[^1],
    casHashes: @[],
    errors: @[],
    warnings: @[]
  )
  
  mm.logger.log(Info, fmt"Migrating Nix package: {nixStorePath}")
  
  if not nixStorePath.startsWith("/nix/store/"):
    result.errors.add("Invalid Nix store path")
    return
  
  if not dirExists(nixStorePath):
    result.errors.add(fmt"Nix store path not found: {nixStorePath}")
    return
  
  # Migrate files from Nix store
  for file in walkDirRec(nixStorePath):
    if not mm.dryRun:
      let storeResult = mm.casManager.storeFile(file)
      if storeResult.isOk:
        result.casHashes.add(storeResult.get().hash)
  
  result.success = result.errors.len == 0

# ############################################################################
# Format Conversion Utilities
# ############################################################################

proc convertNpkToNip*(mm: var MigrationManager, npkPath: string): MigrationResult =
  ## Convert an NPK (binary package) to NIP (application) format
  result = MigrationResult(
    success: false,
    source: OldNip,
    packageName: npkPath.extractFilename.changeFileExt(""),
    casHashes: @[],
    errors: @[]
  )
  
  mm.logger.log(Info, fmt"Converting NPK to NIP: {npkPath}")
  
  # NPK is a binary package, NIP is an application bundle
  # The main difference is the manifest and entry point handling
  
  result.warnings.add("NPK->NIP conversion creates application wrapper")
  result.success = true  # Placeholder

proc convertNipToNexter*(mm: var MigrationManager, nipPath: string): MigrationResult =
  ## Convert a NIP to NEXTER container format
  result = MigrationResult(
    success: false,
    source: OldNip,
    packageName: nipPath.extractFilename,
    casHashes: @[],
    errors: @[]
  )
  
  mm.logger.log(Info, fmt"Converting NIP to NEXTER: {nipPath}")
  
  result.warnings.add("NIP->NEXTER creates containerized environment")
  result.success = true  # Placeholder

# ############################################################################
# Verification Tools
# ############################################################################

proc verifyMigration*(mm: var MigrationManager, migResult: MigrationResult): bool =
  ## Verify that a migration completed successfully
  mm.logger.log(Info, fmt"Verifying migration: {migResult.packageName}")
  
  if not migResult.success:
    mm.logger.log(Error, "Migration reported failure")
    return false
  
  # Verify all CAS objects exist
  var missing = 0
  for hash in migResult.casHashes:
    if not mm.casManager.objectExists(hash):
      mm.logger.log(Error, fmt"Missing CAS object: {hash}")
      missing += 1
  
  if missing > 0:
    mm.logger.log(Error, fmt"{missing} objects missing from CAS")
    return false
  
  mm.logger.log(Info, fmt"Migration verified: {migResult.casHashes.len} objects")
  return true

proc generateMigrationReport*(results: seq[MigrationResult]): string =
  ## Generate a summary report of migration results
  var successCount = 0
  var failCount = 0
  var totalObjects = 0
  
  result = "# Migration Report\n\n"
  let timestamp = now().format("yyyy-MM-dd HH:mm:ss")
  result.add fmt"Generated: {timestamp}" & "\n\n"
  
  for r in results:
    if r.success:
      successCount += 1
      totalObjects += r.casHashes.len
    else:
      failCount += 1
  
  result.add fmt"## Summary\n\n"
  result.add fmt"- Total migrations: {results.len}\n"
  result.add fmt"- Successful: {successCount}\n"
  result.add fmt"- Failed: {failCount}\n"
  result.add fmt"- Total CAS objects: {totalObjects}\n\n"
  
  result.add "## Details\n\n"
  for r in results:
    let status = if r.success: "✅" else: "❌"
    result.add fmt"### {status} {r.packageName}\n\n"
    result.add fmt"- Source: {r.source}\n"
    result.add fmt"- Objects: {r.casHashes.len}\n"
    if r.errors.len > 0:
      result.add "- Errors:\n"
      for err in r.errors:
        result.add fmt"  - {err}\n"
    if r.warnings.len > 0:
      result.add "- Warnings:\n"
      for warn in r.warnings:
        result.add fmt"  - {warn}\n"
    result.add "\n"
