# 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.

## variant_manager.nim
## Orchestration layer for NIP variant management
## Coordinates all variant operations: creation, querying, validation

import std/[tables, options, os]
import variant_types
import variant_domains
import variant_fingerprint
import variant_validator
import variant_parser
import variant_compiler
import variant_paths
import variant_profiles
import variant_database
import config

type
  VariantManager* = ref object
    ## Central coordinator for all variant operations
    db*: VariantDatabase
    domains*: Table[string, FlagDomain]
    defaultToolchain*: ToolchainInfo
    defaultTarget*: TargetInfo

  VariantCreationResult* = object
    ## Result of variant creation
    success*: bool
    fingerprint*: VariantFingerprint
    error*: string
    reusedExisting*: bool  # True if existing variant was reused (Task 14.1)

  VariantConflictResult* = object
    ## Result of variant path conflict check (Task 14.1)
    hasConflict*: bool
    existingFingerprint*: string
    canReuse*: bool
    message*: string

  VariantManagerError* = object of CatchableError

# #############################################################################
# Initialization
# #############################################################################

proc newVariantManager*(dbPath: string): VariantManager =
  ## Create a new variant manager instance
  result = VariantManager(
    db: newVariantDatabase(dbPath),
    domains: SEMANTIC_DOMAINS,
    defaultToolchain: newToolchainInfo("gcc", "13.2.0"),
    defaultTarget: newTargetInfo("x86_64", "linux")
  )

  # Load existing variants from database
  result.db.loadVariants()

proc setDefaultToolchain*(vm: VariantManager, name: string, version: string) =
  ## Set the default toolchain for variant creation
  vm.defaultToolchain = newToolchainInfo(name, version)

proc setDefaultTarget*(vm: VariantManager, arch: string, os: string) =
  ## Set the default target for variant creation
  vm.defaultTarget = newTargetInfo(arch, os)


# #############################################################################
# Variant Coexistence and Conflict Detection (Task 14.1)
# #############################################################################

proc checkVariantPathConflict*(
  vm: VariantManager,
  packageName: string,
  version: string,
  fingerprint: string,
  installPath: string
): VariantConflictResult =
  ## Check if a variant path already exists and handle conflicts
  ## Returns conflict information and whether existing variant can be reused

  result = VariantConflictResult(
    hasConflict: false,
    existingFingerprint: "",
    canReuse: false,
    message: ""
  )

  # Check if path exists on filesystem
  if dirExists(installPath):
    # Path exists - check if it's the same variant
    let existingVariant = vm.db.queryVariantByPath(installPath)

    if existingVariant.isSome:
      # Found variant in database
      if existingVariant.get().fingerprint == fingerprint:
        # Same fingerprint - can reuse
        result.hasConflict = true
        result.existingFingerprint = fingerprint
        result.canReuse = true
        result.message = "Variant already installed at path (fingerprint matches)"
      else:
        # Different fingerprint - this should never happen!
        result.hasConflict = true
        result.existingFingerprint = existingVariant.get().fingerprint
        result.canReuse = false
        result.message = "CRITICAL: Path exists with different fingerprint! Expected: " &
                        fingerprint & ", Found: " & existingVariant.get().fingerprint
    else:
      # Path exists but not in database - orphaned installation
      result.hasConflict = true
      result.existingFingerprint = ""
      result.canReuse = false
      result.message = "Path exists but variant not in database (orphaned installation)"

  # Check if fingerprint exists in database (different path)
  let dbVariant = vm.db.queryVariantByFingerprint(fingerprint)
  if dbVariant.isSome and dbVariant.get().installPath != installPath:
    # Fingerprint exists but at different path - database inconsistency
    result.hasConflict = true
    result.existingFingerprint = fingerprint
    result.canReuse = true  # Can reuse the existing one
    result.message = "Variant exists at different path: " & dbVariant.get().installPath

proc reuseExistingVariant*(
  vm: VariantManager,
  fingerprint: string
): VariantCreationResult =
  ## Reuse an existing variant instead of creating a new one

  let existing = vm.db.queryVariantByFingerprint(fingerprint)
  if existing.isNone:
    return VariantCreationResult(
      success: false,
      error: "Cannot reuse: variant not found in database",
      reusedExisting: false
    )

  let existingRecord = existing.get()
  # Convert VariantRecord to VariantFingerprint
  var variantFp = VariantFingerprint(
    packageName: existingRecord.packageName,
    version: existingRecord.version,
    domainFlags: existingRecord.domains,
    compilerFlags: CompilerFlags(cflags: "", cxxflags: "", ldflags: "", makeflags: ""),
    toolchain: existingRecord.toolchain,
    target: existingRecord.target,
    hash: existingRecord.fingerprint
  )

  return VariantCreationResult(
    success: true,
    fingerprint: variantFp,
    error: "",
    reusedExisting: true
  )


# #############################################################################
# Variant Creation Workflow
# #############################################################################

proc createVariant*(
  vm: VariantManager,
  packageName: string,
  version: string,
  domainFlags: Table[string, seq[string]],
  compilerFlags: CompilerFlags,
  toolchain: Option[ToolchainInfo] = none(ToolchainInfo),
  target: Option[TargetInfo] = none(TargetInfo)
): VariantCreationResult =
  ## Create a new variant with full workflow:
  ## 1. Validate domain configuration
  ## 2. Resolve compiler flags
  ## 3. Calculate fingerprint
  ## 4. Generate installation path
  ## 5. Store in database

  # Step 1: Validate domain configuration
  let validation = validateDomainConfig(domainFlags)
  if not validation.isOk:
    return VariantCreationResult(
      success: false,
      error: "Domain validation failed: " & validation.error.msg
    )

  # Step 2: Resolve compiler flags from domains
  let resolvedFlags = resolveCompilerFlags(domainFlags, compilerFlags)

  # Step 3: Determine toolchain and target
  let actualToolchain = if toolchain.isSome(): toolchain.get() else: vm.defaultToolchain
  let actualTarget = if target.isSome(): target.get() else: vm.defaultTarget

  # Step 4: Calculate variant fingerprint
  let fingerprint = calculateVariantFingerprint(
    packageName,
    version,
    domainFlags,
    resolvedFlags,
    actualToolchain,
    actualTarget
  )

  # Step 5: Generate installation path
  let installPath = generateVariantPath(packageName, version, fingerprint)

  # Step 5.5: Check for path conflicts (Task 14.1)
  let conflictCheck = vm.checkVariantPathConflict(
    packageName,
    version,
    fingerprint,
    installPath
  )

  if conflictCheck.hasConflict:
    if conflictCheck.canReuse:
      # Reuse existing variant
      return vm.reuseExistingVariant(fingerprint)
    else:
      # Cannot proceed - critical conflict
      return VariantCreationResult(
        success: false,
        error: conflictCheck.message,
        reusedExisting: false
      )

  # Step 6: Create variant fingerprint object
  var variantFp = VariantFingerprint(
    packageName: packageName,
    version: version,
    domainFlags: domainFlags,
    compilerFlags: resolvedFlags,
    toolchain: actualToolchain,
    target: actualTarget,
    hash: fingerprint
  )

  # Step 7: Store in database
  let stored = vm.db.createVariantRecord(
    fingerprint,
    packageName,
    version,
    domainFlags,
    installPath,
    actualToolchain,
    actualTarget
  )

  if not stored:
    # Variant already exists in database - reuse it (Task 14.1)
    return vm.reuseExistingVariant(fingerprint)

  return VariantCreationResult(
    success: true,
    fingerprint: variantFp,
    error: "",
    reusedExisting: false
  )


proc createVariantFromProfile*(
  vm: VariantManager,
  packageName: string,
  version: string,
  profile: VariantProfile,
  overrides: Table[string, seq[string]] = initTable[string, seq[string]](),
  compilerOverrides: Option[CompilerFlags] = none(CompilerFlags)
): VariantCreationResult =
  ## Create a variant from a profile with optional overrides

  # Merge profile with overrides
  let mergedProfile = mergeProfileWithOverrides(profile, overrides, compilerOverrides)

  # Create variant using merged configuration
  return vm.createVariant(
    packageName,
    version,
    mergedProfile.domains,
    mergedProfile.compilerFlags
  )

proc createVariantFromFlags*(
  vm: VariantManager,
  packageName: string,
  version: string,
  flagStrings: seq[string],
  baseCompilerFlags: CompilerFlags
): VariantCreationResult =
  ## Create a variant from CLI flag strings
  ## Parses domain flags and creates variant

  var domainFlags = initTable[string, seq[string]]()

  # Parse each flag string
  for flagStr in flagStrings:
    try:
      let flag = parseDomainFlag(flagStr)
      # Add or append to domain
      if domainFlags.hasKey(flag.domain):
        domainFlags[flag.domain].add(flag.name)
      else:
        domainFlags[flag.domain] = @[flag.name]
    except ParseError as e:
      return VariantCreationResult(
        success: false,
        error: "Invalid flag: " & flagStr & " - " & e.msg
      )

  return vm.createVariant(
    packageName,
    version,
    domainFlags,
    baseCompilerFlags
  )


# #############################################################################
# Variant Query Operations
# #############################################################################

proc listVariants*(vm: VariantManager, packageName: string): seq[VariantRecord] =
  ## List all variants for a specific package
  vm.db.queryVariantsByPackage(packageName)

proc getVariantInfo*(vm: VariantManager, fingerprint: string): Option[VariantRecord] =
  ## Get detailed information about a variant
  return vm.db.queryVariantByFingerprint(fingerprint)

proc calculateVariantId*(
  vm: VariantManager,
  packageName: string,
  version: string,
  domainFlags: Table[string, seq[string]],
  compilerFlags: CompilerFlags,
  toolchain: Option[ToolchainInfo] = none(ToolchainInfo),
  target: Option[TargetInfo] = none(TargetInfo)
): string =
  ## Calculate variant fingerprint without creating/installing
  ## Useful for checking if a variant already exists

  let actualToolchain = if toolchain.isSome(): toolchain.get() else: vm.defaultToolchain
  let actualTarget = if target.isSome(): target.get() else: vm.defaultTarget

  # Resolve compiler flags
  let resolvedFlags = resolveCompilerFlags(domainFlags, compilerFlags)

  return calculateVariantFingerprint(
    packageName,
    version,
    domainFlags,
    resolvedFlags,
    actualToolchain,
    actualTarget
  )

proc hasVariant*(vm: VariantManager, fingerprint: string): bool =
  ## Check if a variant exists
  vm.db.hasVariant(fingerprint)

proc deleteVariant*(vm: VariantManager, fingerprint: string): bool =
  ## Delete a variant from the database
  let (success, _) = vm.db.deleteVariantWithReferences(fingerprint)
  return success

proc countVariants*(vm: VariantManager, packageName: string): int =
  ## Count variants for a package
  vm.db.countVariantsByPackage(packageName)

proc listAllVariants*(vm: VariantManager): seq[VariantRecord] =
  ## List all variants in the database
  vm.db.listAllVariants()


# #############################################################################
# Utility Functions
# #############################################################################

proc `$`*(cr: VariantCreationResult): string =
  ## String representation of creation result
  if cr.success:
    result = "Success: " & cr.fingerprint.hash
  else:
    result = "Failed: " & cr.error

proc prettyPrintVariant*(vm: VariantManager, fingerprint: string): string =
  ## Pretty print variant information
  let info = vm.getVariantInfo(fingerprint)
  if info.isSome():
    return prettyPrint(info.get())
  else:
    return "Variant not found: " & fingerprint

proc validateFlags*(vm: VariantManager, domainFlags: Table[string, seq[string]]): ValidationResult[bool] =
  ## Validate domain flags without creating a variant
  validateDomainConfig(domainFlags)

proc getDomainInfo*(vm: VariantManager, domainName: string): Option[FlagDomain] =
  ## Get information about a domain
  if vm.domains.hasKey(domainName):
    return some(vm.domains[domainName])
  else:
    return none(FlagDomain)

proc listDomains*(vm: VariantManager): seq[string] =
  ## List all available domains
  result = @[]
  for domain in vm.domains.keys:
    result.add(domain)
