import std/[os, osproc, strformat, logging, tempfiles]
import zstd/compress
import zstd/decompress
import nip/manifest_parser

type
  ArchiveError* = object of CatchableError

proc runCmd(cmd: string) =
  let res = execCmdEx(cmd)
  if res.exitCode != 0:
    raise newException(ArchiveError, fmt"Command failed: {cmd}{'\n'}Output: {res.output}")

proc createArchive*(manifest: PackageManifest, sourceDir: string,
    outputFile: string) =
  ## Create a .nip archive from a source directory and manifest.
  ## The archive will contain:
  ## - manifest.kdl
  ## - files/ (content of sourceDir)

  info(fmt"Creating archive {outputFile} from {sourceDir}")

  let tempDir = createTempDir("nip_build_", "")
  defer: removeDir(tempDir)

  # 1. Write manifest to temp root
  let manifestPath = tempDir / "manifest.kdl"
  writeFile(manifestPath, serializeManifestToKDL(manifest))

  # 2. Copy source files to temp/files
  let filesDir = tempDir / "files"
  createDir(filesDir)
  copyDirWithPermissions(sourceDir, filesDir)

  # 3. Create Tar (Uncompressed)
  let tarFile = tempDir / "archive.tar"
  let cmd = fmt"tar -C {tempDir.quoteShell} -cf {tarFile.quoteShell} manifest.kdl files"
  runCmd(cmd)

  # 4. Compress with Zstd (Internal)
  # TODO: Use streaming for large files
  info "Compressing archive (Zstd Internal)..."
  let content = readFile(tarFile)
  # level 3 is default
  let compressedSeq = compress(content, level = 3)
  let compressedStr = cast[string](compressedSeq)
  writeFile(outputFile, compressedStr)

  info(fmt"Archive created successfully: {outputFile}")

proc extractArchive*(archivePath: string, targetDir: string) =
  ## Extract a .nip archive to targetDir.
  ## Decompress using internal Zstd, then untar using shell.

  info(fmt"Extracting archive {archivePath} to {targetDir}")
  createDir(targetDir)

  # 1. Decompress (Internal)
  info "Decompressing archive (Zstd Internal)..."
  let content = readFile(archivePath)
  let decompressedSeq = decompress(content)
  let decompressedStr = cast[string](decompressedSeq)

  let tarFile = targetDir / "temp_extract.tar"
  writeFile(tarFile, decompressedStr)

  # 2. Untar (Shell)
  let cmd = fmt"tar -C {targetDir.quoteShell} -xf {tarFile.quoteShell}"
  runCmd(cmd)

  removeFile(tarFile)

  info("Extraction complete")

proc verifyArchive*(archivePath: string): bool =
  ## Verify archive integrity (zstd check)
  # TODO: Use library verify? For now try decompressing to void
  try:
    let content = readFile(archivePath)
    discard decompress(content)
    return true
  except:
    return false
