EoL-Versioning.ps1

# ============================================================================
# MODULE DE VERSIONING - Base de données EoL
# ============================================================================
# Gestion des versions et de l'historique des mises à jour
# Dépendance : EoL-Functions.ps1 (chargé automatiquement si absent)
# ============================================================================

#region Dépendances
# Get-EoLDatabasePath est défini dans EoL-Functions.ps1
# Ce guard permet l'utilisation de EoL-Versioning.ps1 de manière autonome
if (-not (Get-Command Get-EoLDatabasePath -ErrorAction SilentlyContinue)) {
    $eolFunctionsPath = Join-Path $PSScriptRoot "EoL-Functions.ps1"
    if (Test-Path $eolFunctionsPath) {
        . $eolFunctionsPath
        Write-Verbose "EoL-Functions.ps1 chargé automatiquement par EoL-Versioning.ps1"
    } else {
        throw "EoL-Versioning.ps1 requiert EoL-Functions.ps1 dans le même répertoire : $eolFunctionsPath"
    }
}
#endregion

#region Configuration
$script:HistoryFolder = ".eol-history"
$script:MaxHistoryVersions = 10  # Nombre de versions à conserver
#endregion

#region Fonctions de versioning

<#
.SYNOPSIS
Crée une sauvegarde versionnée de la base de données EoL.

.DESCRIPTION
Sauvegarde le fichier JSON actuel avec un timestamp et un numéro de version.

.PARAMETER SourcePath
Chemin du fichier JSON source

.PARAMETER Reason
Raison de la sauvegarde (ex: "Mise à jour API", "Ajout manuel")

.OUTPUTS
String - Chemin du fichier de sauvegarde créé
#>

function Backup-EoLDatabase {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [string]$SourcePath,
        
        [Parameter(Mandatory = $false)]
        [string]$Reason = "Sauvegarde manuelle"
    )
    
    if (-not $SourcePath) {
        $SourcePath = Get-EoLDatabasePath
    }
    
    if (-not (Test-Path $SourcePath)) {
        Write-Error "Fichier source introuvable: $SourcePath"
        return $null
    }
    
    # Créer le dossier d'historique si nécessaire
    $historyPath = Join-Path (Split-Path $SourcePath -Parent) $script:HistoryFolder
    if (-not (Test-Path $historyPath)) {
        New-Item -Path $historyPath -ItemType Directory -Force | Out-Null
        Write-Verbose "Dossier d'historique créé: $historyPath"
    }
    
    # Lire le JSON pour obtenir la version
    try {
        $jsonContent = Get-Content -Path $SourcePath -Raw -Encoding UTF8
        $database = $jsonContent | ConvertFrom-Json
        $version = $database.metadata.version
    } catch {
        $version = "unknown"
    }
    
    # Créer le nom du fichier de backup
    $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
    $backupFileName = "eol-database_v${version}_${timestamp}.json"
    $backupPath = Join-Path $historyPath $backupFileName
    
    # Copier le fichier
    Copy-Item -Path $SourcePath -Destination $backupPath -Force
    
    # Créer le fichier de métadonnées
    $metaFileName = "eol-database_v${version}_${timestamp}.meta.json"
    $metaPath = Join-Path $historyPath $metaFileName
    
    $metadata = @{
        version = $version
        timestamp = $timestamp
        datetime = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
        reason = $Reason
        original_file = (Split-Path $SourcePath -Leaf)
        os_count = $database.os_database.PSObject.Properties.Count
    }
    
    $metadata | ConvertTo-Json | Set-Content -Path $metaPath -Encoding UTF8
    
    Write-Host "💾 Sauvegarde créée: $backupFileName" -ForegroundColor Green
    Write-Verbose "Raison: $Reason"
    Write-Verbose "Version: $version"
    Write-Verbose "Nombre d'OS: $($metadata.os_count)"
    
    # Nettoyer les anciennes versions si nécessaire
    Clear-OldBackups -HistoryPath $historyPath -KeepVersions $script:MaxHistoryVersions
    
    return $backupPath
}

<#
.SYNOPSIS
Nettoie les anciennes sauvegardes pour ne garder que les N plus récentes.

.PARAMETER HistoryPath
Chemin du dossier d'historique

.PARAMETER KeepVersions
Nombre de versions à conserver
#>

function Clear-OldBackups {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$HistoryPath,
        
        [Parameter(Mandatory = $false)]
        [int]$KeepVersions = 10
    )
    
    if (-not (Test-Path $HistoryPath)) {
        return
    }
    
    # Récupérer tous les fichiers JSON de backup (pas les .meta)
    $backups = Get-ChildItem -Path $HistoryPath -Filter "eol-database_v*.json" | 
               Where-Object { $_.Name -notlike "*.meta.json" } |
               Sort-Object CreationTime -Descending
    
    if ($backups.Count -le $KeepVersions) {
        Write-Verbose "Nombre de backups ($($backups.Count)) <= $KeepVersions, aucun nettoyage nécessaire"
        return
    }
    
    # Supprimer les anciennes versions
    $toDelete = $backups | Select-Object -Skip $KeepVersions
    
    foreach ($file in $toDelete) {
        $baseName = $file.BaseName
        
        # Supprimer le fichier JSON
        Remove-Item -Path $file.FullName -Force
        
        # Supprimer le fichier .meta associé
        $metaFile = Join-Path $HistoryPath "$baseName.meta.json"
        if (Test-Path $metaFile) {
            Remove-Item -Path $metaFile -Force
        }
        
        Write-Verbose "🗑️ Supprimé: $($file.Name)"
    }
    
    Write-Host "🧹 Nettoyage: $($toDelete.Count) ancienne(s) version(s) supprimée(s)" -ForegroundColor Gray
}

<#
.SYNOPSIS
Liste toutes les versions de backup disponibles.

.PARAMETER ShowDetails
Affiche les détails de chaque version

.OUTPUTS
Array - Liste des backups disponibles
#>

function Get-EoLBackupHistory {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $false)]
        [switch]$ShowDetails
    )
    
    $jsonPath = Get-EoLDatabasePath
    $historyPath = Join-Path (Split-Path $jsonPath -Parent) $script:HistoryFolder
    
    if (-not (Test-Path $historyPath)) {
        Write-Host "📁 Aucun historique de sauvegarde trouvé" -ForegroundColor Yellow
        return @()
    }
    
    $backups = Get-ChildItem -Path $historyPath -Filter "eol-database_v*.json" |
               Where-Object { $_.Name -notlike "*.meta.json" } |
               Sort-Object CreationTime -Descending
    
    if ($backups.Count -eq 0) {
        Write-Host "📁 Aucune sauvegarde trouvée" -ForegroundColor Yellow
        return @()
    }
    
    Write-Host ""
    Write-Host "📚 HISTORIQUE DES VERSIONS" -ForegroundColor Cyan
    Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
    
    $results = @()
    
    foreach ($backup in $backups) {
        # Charger les métadonnées
        $metaPath = Join-Path $historyPath "$($backup.BaseName).meta.json"
        
        if (Test-Path $metaPath) {
            $meta = Get-Content -Path $metaPath -Raw | ConvertFrom-Json
            
            $result = [PSCustomObject]@{
                Version = $meta.version
                Date = $meta.datetime
                Reason = $meta.reason
                OSCount = $meta.os_count
                File = $backup.Name
                Path = $backup.FullName
                Size = [math]::Round($backup.Length / 1KB, 2)
            }
            
            if ($ShowDetails) {
                Write-Host ""
                Write-Host " 📌 Version $($meta.version)" -ForegroundColor White
                Write-Host " Date : $($meta.datetime)" -ForegroundColor Gray
                Write-Host " Raison : $($meta.reason)" -ForegroundColor Gray
                Write-Host " OS : $($meta.os_count)" -ForegroundColor Gray
                Write-Host " Taille : $($result.Size) KB" -ForegroundColor Gray
                Write-Host " Fichier : $($backup.Name)" -ForegroundColor DarkGray
            }
            
            $results += $result
        } else {
            # Pas de métadonnées, afficher info basique
            $result = [PSCustomObject]@{
                Version = "unknown"
                Date = $backup.CreationTime.ToString("yyyy-MM-dd HH:mm:ss")
                Reason = "N/A"
                OSCount = "N/A"
                File = $backup.Name
                Path = $backup.FullName
                Size = [math]::Round($backup.Length / 1KB, 2)
            }
            
            if ($ShowDetails) {
                Write-Host ""
                Write-Host " 📌 $($backup.Name)" -ForegroundColor White
                Write-Host " Date : $($backup.CreationTime)" -ForegroundColor Gray
                Write-Host " Taille : $($result.Size) KB" -ForegroundColor Gray
            }
            
            $results += $result
        }
    }
    
    if (-not $ShowDetails) {
        Write-Host ""
        $results | Format-Table Version, Date, Reason, OSCount, @{Label="Taille (KB)";Expression={$_.Size}} -AutoSize
    }
    
    Write-Host ""
    Write-Host "💡 Total: $($backups.Count) version(s) | Max conservé: $script:MaxHistoryVersions" -ForegroundColor Gray
    Write-Host "💡 Pour restaurer: Restore-EoLDatabase -BackupPath <chemin>" -ForegroundColor Gray
    
    return $results
}

<#
.SYNOPSIS
Restaure une version spécifique de la base de données.

.PARAMETER BackupPath
Chemin du fichier de backup à restaurer

.PARAMETER Force
Force la restauration sans demander confirmation

.EXAMPLE
Restore-EoLDatabase -BackupPath ".eol-history/eol-database_v2.0_20250215_143022.json"
#>

function Restore-EoLDatabase {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$BackupPath,
        
        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    
    if (-not (Test-Path $BackupPath)) {
        Write-Error "Fichier de backup introuvable: $BackupPath"
        return
    }
    
    # Valider que c'est un JSON valide
    try {
        $backupContent = Get-Content -Path $BackupPath -Raw -Encoding UTF8
        $backupData = $backupContent | ConvertFrom-Json
    } catch {
        Write-Error "Fichier de backup invalide: $($_.Exception.Message)"
        return
    }
    
    # Afficher les infos de la version à restaurer
    Write-Host ""
    Write-Host "📦 RESTAURATION DE VERSION" -ForegroundColor Cyan
    Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
    Write-Host ""
    Write-Host " Version : $($backupData.metadata.version)" -ForegroundColor White
    Write-Host " Dernière MAJ: $($backupData.metadata.last_updated)" -ForegroundColor Gray
    Write-Host " Nombre d'OS : $($backupData.os_database.PSObject.Properties.Count)" -ForegroundColor Gray
    Write-Host " Source : $BackupPath" -ForegroundColor DarkGray
    Write-Host ""
    
    # Demander confirmation si pas -Force
    if (-not $Force) {
        $currentPath = Get-EoLDatabasePath
        if (Test-Path $currentPath) {
            $currentData = Get-Content -Path $currentPath -Raw | ConvertFrom-Json
            Write-Warning "La base actuelle (v$($currentData.metadata.version)) sera remplacée !"
        }
        
        $response = Read-Host "Confirmer la restauration ? (O/N)"
        if ($response -ne "O" -and $response -ne "o") {
            Write-Host "❌ Restauration annulée" -ForegroundColor Yellow
            return
        }
    }
    
    # Sauvegarder la version actuelle avant restauration
    $currentPath = Get-EoLDatabasePath
    if (Test-Path $currentPath) {
        Write-Host "💾 Sauvegarde de la version actuelle..." -ForegroundColor Gray
        Backup-EoLDatabase -SourcePath $currentPath -Reason "Avant restauration vers v$($backupData.metadata.version)"
    }
    
    # Restaurer
    try {
        Copy-Item -Path $BackupPath -Destination $currentPath -Force
        Write-Host "✅ Version restaurée avec succès !" -ForegroundColor Green
        Write-Host ""
        Write-Host "📊 Base actuelle:" -ForegroundColor Cyan
        Write-Host " Version : $($backupData.metadata.version)" -ForegroundColor White
        Write-Host " OS : $($backupData.os_database.PSObject.Properties.Count)" -ForegroundColor White
        
    } catch {
        Write-Error "Erreur lors de la restauration: $($_.Exception.Message)"
    }
}

<#
.SYNOPSIS
Compare deux versions de la base de données.

.PARAMETER Version1Path
Chemin de la première version

.PARAMETER Version2Path
Chemin de la deuxième version (par défaut: version actuelle)

.EXAMPLE
Compare-EoLVersions -Version1Path ".eol-history/eol-database_v2.0_20250215.json"
#>

function Compare-EoLVersions {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version1Path,
        
        [Parameter(Mandatory = $false)]
        [string]$Version2Path
    )
    
    if (-not $Version2Path) {
        $Version2Path = Get-EoLDatabasePath
    }
    
    if (-not (Test-Path $Version1Path)) {
        Write-Error "Version 1 introuvable: $Version1Path"
        return
    }
    
    if (-not (Test-Path $Version2Path)) {
        Write-Error "Version 2 introuvable: $Version2Path"
        return
    }
    
    # Charger les deux versions
    $v1 = Get-Content -Path $Version1Path -Raw | ConvertFrom-Json
    $v2 = Get-Content -Path $Version2Path -Raw | ConvertFrom-Json
    
    $v1OS = $v1.os_database.PSObject.Properties.Name
    $v2OS = $v2.os_database.PSObject.Properties.Name
    
    # Calculer les différences
    $added = $v2OS | Where-Object { $v1OS -notcontains $_ }
    $removed = $v1OS | Where-Object { $v2OS -notcontains $_ }
    $common = $v1OS | Where-Object { $v2OS -contains $_ }
    
    # OS modifiés
    $modified = @()
    foreach ($os in $common) {
        $v1Info = $v1.os_database.$os
        $v2Info = $v2.os_database.$os
        
        if ($v1Info.eol_date -ne $v2Info.eol_date -or 
            $v1Info.extended_support -ne $v2Info.extended_support) {
            $modified += $os
        }
    }
    
    # Affichage
    Write-Host ""
    Write-Host "🔍 COMPARAISON DE VERSIONS" -ForegroundColor Cyan
    Write-Host "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" -ForegroundColor Cyan
    Write-Host ""
    Write-Host " 📌 Version 1: $($v1.metadata.version) ($($v1.metadata.last_updated)) - $($v1OS.Count) OS" -ForegroundColor White
    Write-Host " 📌 Version 2: $($v2.metadata.version) ($($v2.metadata.last_updated)) - $($v2OS.Count) OS" -ForegroundColor White
    Write-Host ""
    
    if ($added.Count -gt 0) {
        Write-Host " ✅ OS ajoutés ($($added.Count)):" -ForegroundColor Green
        $added | ForEach-Object { Write-Host " + $_" -ForegroundColor Green }
        Write-Host ""
    }
    
    if ($removed.Count -gt 0) {
        Write-Host " ❌ OS supprimés ($($removed.Count)):" -ForegroundColor Red
        $removed | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
        Write-Host ""
    }
    
    if ($modified.Count -gt 0) {
        Write-Host " 🔄 OS modifiés ($($modified.Count)):" -ForegroundColor Yellow
        foreach ($os in $modified) {
            Write-Host " ~ $os" -ForegroundColor Yellow
            $v1Info = $v1.os_database.$os
            $v2Info = $v2.os_database.$os
            
            if ($v1Info.eol_date -ne $v2Info.eol_date) {
                Write-Host " EoL: $($v1Info.eol_date) → $($v2Info.eol_date)" -ForegroundColor Gray
            }
            if ($v1Info.extended_support -ne $v2Info.extended_support) {
                Write-Host " Ext: $($v1Info.extended_support) → $($v2Info.extended_support)" -ForegroundColor Gray
            }
        }
        Write-Host ""
    }
    
    if ($added.Count -eq 0 -and $removed.Count -eq 0 -and $modified.Count -eq 0) {
        Write-Host " ✅ Aucune différence détectée" -ForegroundColor Green
        Write-Host ""
    }
    
    # Résumé
    return [PSCustomObject]@{
        Version1 = $v1.metadata.version
        Version2 = $v2.metadata.version
        Added = $added.Count
        Removed = $removed.Count
        Modified = $modified.Count
        Total_V1 = $v1OS.Count
        Total_V2 = $v2OS.Count
    }
}

#endregion