Imports System.IO
Imports System.Runtime.InteropServices
Imports DigitalData.Modules.Logging
Public Class Map_Drive
    Private ReadOnly _Logger As Logger
    Private ReadOnly _LogConfig As LogConfig
#Region "Windows API Deklarationen"
    <DllImport("mpr.dll", CharSet:=CharSet.Auto)>
    Private Shared Function WNetAddConnection2(ByRef lpNetResource As NETRESOURCE,
                                                ByVal lpPassword As String,
                                                ByVal lpUsername As String,
                                                ByVal dwFlags As Integer) As Integer
    End Function

    <DllImport("mpr.dll", CharSet:=CharSet.Auto)>
    Private Shared Function WNetCancelConnection2(ByVal lpName As String,
                                                    ByVal dwFlags As Integer,
                                                    ByVal fForce As Boolean) As Integer
    End Function

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
    Private Structure NETRESOURCE
        Public dwScope As Integer
        Public dwType As Integer
        Public dwDisplayType As Integer
        Public dwUsage As Integer
        Public lpLocalName As String
        Public lpRemoteName As String
        Public lpComment As String
        Public lpProvider As String
    End Structure

    Private Const RESOURCETYPE_DISK As Integer = 1
    Private Const CONNECT_UPDATE_PROFILE As Integer = 1
    Private Const ERROR_SUCCESS As Integer = 0
    Private Const ERROR_ALREADY_ASSIGNED As Integer = 85
#End Region
    Public Sub New(LogConfig As LogConfig)
        _LogConfig = LogConfig
        _Logger = LogConfig.GetLogger()
    End Sub
    ''' <summary>
    ''' Struktur für Netzlaufwerk-Informationen
    ''' </summary>
    Public Structure NetworkDriveInfo
        Public DriveLetter As String
        Public NetworkPath As String
        Public DriveType As IO.DriveType
        Public IsReady As Boolean
        Public TotalSize As Long
        Public FreeSpace As Long
    End Structure

    ''' <summary>
    ''' Ermittelt den nächsten freien Laufwerksbuchstaben (alphabetisch absteigend von Z bis A)
    ''' </summary>
    ''' <param name="blacklist">Liste der nicht erlaubten Laufwerksbuchstaben (z.B. "Y,M,V")</param>
    ''' <returns>Nächster freier Laufwerksbuchstabe mit Doppelpunkt (z.B. "Z:") oder String.Empty wenn keiner frei</returns>
    Public Function GetNextFreeDriveLetter(Optional blacklist As String = "") As String
        Try
            ' Blacklist verarbeiten (Großbuchstaben ohne Doppelpunkte)
            Dim blacklistArray As New List(Of Char)
            If Not String.IsNullOrEmpty(blacklist) Then
                For Each item In blacklist.Split(","c)
                    Dim letter = item.Trim().ToUpper().Replace(":", "")
                    If letter.Length = 1 AndAlso Char.IsLetter(letter(0)) Then
                        blacklistArray.Add(letter(0))
                    End If
                Next
            End If

            ' Alle aktuell verwendeten Laufwerksbuchstaben ermitteln
            Dim usedDrives As New List(Of Char)
            For Each drive As IO.DriveInfo In IO.DriveInfo.GetDrives()
                Dim letter As Char = drive.Name(0)
                usedDrives.Add(Char.ToUpper(letter))
            Next

            ' Alphabetisch absteigend von Z bis A durchgehen
            For i As Integer = Asc("Z"c) To Asc("A"c) Step -1
                Dim currentLetter As Char = Chr(i)

                ' Prüfen ob Buchstabe verfügbar ist
                If Not usedDrives.Contains(currentLetter) AndAlso Not blacklistArray.Contains(currentLetter) Then
                    _Logger.Debug($"Nächster freier Laufwerksbuchstabe gefunden: {currentLetter}:")
                    Return currentLetter & ":"
                End If
            Next

            _Logger.Warn("Kein freier Laufwerksbuchstabe gefunden!")
            Return String.Empty

        Catch ex As Exception
            _Logger.Error($"Fehler beim Ermitteln des nächsten freien Laufwerksbuchstabens: {ex.Message}")
            _Logger.Error(ex)
            Return String.Empty
        End Try
    End Function

    ''' <summary>
    ''' Prüft ob ein Laufwerksbuchstabe verfügbar ist (nicht verwendet und nicht in Blacklist)
    ''' </summary>
    ''' <param name="driveLetter">Zu prüfender Laufwerksbuchstabe</param>
    ''' <param name="blacklist">Liste der nicht erlaubten Laufwerksbuchstaben</param>
    ''' <returns>True wenn verfügbar, False wenn bereits verwendet oder in Blacklist</returns>
    Public Function IsDriveLetterAvailable(driveLetter As String, Optional blacklist As String = "") As Boolean
        Try
            ' Formatierung sicherstellen
            driveLetter = driveLetter.Trim().ToUpper().Replace(":", "")
            If driveLetter.Length <> 1 OrElse Not Char.IsLetter(driveLetter(0)) Then
                _Logger.Warn($"Ungültiger Laufwerksbuchstabe: {driveLetter}")
                Return False
            End If

            Dim letter As Char = driveLetter(0)

            ' Blacklist prüfen
            If Not String.IsNullOrEmpty(blacklist) Then
                For Each item In blacklist.Split(","c)
                    Dim blacklistedLetter = item.Trim().ToUpper().Replace(":", "")
                    If blacklistedLetter.Length = 1 AndAlso blacklistedLetter(0) = letter Then
                        _Logger.Debug($"Laufwerk {letter}: ist in der Blacklist")
                        Return False
                    End If
                Next
            End If

            ' Prüfen ob bereits verwendet
            For Each drive As IO.DriveInfo In IO.DriveInfo.GetDrives()
                If Char.ToUpper(drive.Name(0)) = letter Then
                    _Logger.Debug($"Laufwerk {letter}: ist bereits verwendet")
                    Return False
                End If
            Next

            Return True

        Catch ex As Exception
            _Logger.Error($"Fehler beim Prüfen der Laufwerksverfügbarkeit: {ex.Message}")
            _Logger.Error(ex)
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Mappt ein Netzlaufwerk mit automatischer Laufwerksbuchstabenwahl oder spezifischem Buchstaben
    ''' </summary>
    ''' <param name="driveLetter">Gewünschter Laufwerksbuchstabe (leer = automatisch den nächsten freien wählen)</param>
    ''' <param name="networkPath">UNC-Pfad des Netzwerkshares</param>
    ''' <param name="blacklist">Komma-getrennte Liste verbotener Laufwerksbuchstaben (z.B. "Y,M,V")</param>
    ''' <param name="userName">Optionaler Benutzername für Authentifizierung</param>
    ''' <param name="password">Optionales Passwort für Authentifizierung</param>
    ''' <param name="persistent">Soll das Mapping persistent sein?</param>
    ''' <returns>Verwendeter Laufwerksbuchstabe bei Erfolg, String.Empty bei Fehler</returns>
    Public Function MapNetworkDrive(driveLetter As String,
                                      networkPath As String,
                                      Optional blacklist As String = "",
                                      Optional userName As String = Nothing,
                                      Optional password As String = Nothing,
                                      Optional persistent As Boolean = True) As String
        Try
            Dim targetDriveLetter As String = ""

            ' Szenario 1: Kein Laufwerksbuchstabe angegeben -> Automatische Auswahl
            If String.IsNullOrEmpty(driveLetter) Then
                _Logger.Info("Kein Laufwerksbuchstabe angegeben - suche nächsten freien Buchstaben...")
                targetDriveLetter = GetNextFreeDriveLetter(blacklist)

                If String.IsNullOrEmpty(targetDriveLetter) Then
                    _Logger.Error("❌ Kein freier Laufwerksbuchstabe verfügbar!")
                    Return String.Empty
                End If

                _Logger.Info($"Automatisch gewählter Laufwerksbuchstabe: {targetDriveLetter}")
            Else
                ' Szenario 2: Spezifischer Laufwerksbuchstabe angegeben
                targetDriveLetter = driveLetter.Trim().ToUpper()
                If Not targetDriveLetter.EndsWith(":") Then
                    targetDriveLetter &= ":"
                End If

                ' ========== NEU: Prüfen ob Laufwerk verfügbar ist ==========
                If Not IsDriveLetterAvailable(targetDriveLetter, blacklist) Then
                    _Logger.Warn($"⚠️ Laufwerk {targetDriveLetter} ist nicht verfügbar (bereits verwendet oder in Blacklist)")
                    ' NICHT abbrechen - weiter versuchen (alte Logik beibehalten)
                End If
                ' ========== ENDE NEU ==========
            End If

            ' ========== NEU: Prüfung ob Laufwerk bereits existiert ==========
            Dim driveExists As Boolean = False
            Try
                Dim driveInfo As New System.IO.DriveInfo(targetDriveLetter)
                driveExists = driveInfo.IsReady
            Catch
                ' Laufwerk existiert nicht - das ist OK
                driveExists = False
            End Try

            ' Nur trennen wenn Laufwerk wirklich existiert
            If driveExists Then
                _Logger.Debug($"ℹ️ Laufwerk {targetDriveLetter} existiert bereits - wird getrennt")
                DisconnectNetworkDrive(targetDriveLetter, force:=True)
            Else
                _Logger.Debug($"✓ Laufwerk {targetDriveLetter} existiert noch nicht - kein Disconnect nötig")
            End If
            ' ========== ENDE NEU ==========

            ' Laufwerk mappen (bestehende Logik)
            If MapNetworkDriveInternal(targetDriveLetter, networkPath, userName, password, persistent) Then
                _Logger.Info($"✓ Netzlaufwerk {targetDriveLetter} erfolgreich gemappt zu {networkPath}")
                Return targetDriveLetter
            Else
                _Logger.Error($"❌ Fehler beim Mappen von {targetDriveLetter}")
                Return String.Empty
            End If

        Catch ex As Exception
            _Logger.Error($"❌ Fehler in MapNetworkDrive: {ex.Message}")
            _Logger.Error(ex)
            Return String.Empty
        End Try
    End Function
    ''' <summary>
    ''' Interne Methode zum tatsächlichen Mappen eines Netzlaufwerks
    ''' </summary>
    Private Function MapNetworkDriveInternal(driveLetter As String,
                                                    networkPath As String,
                                                    userName As String,
                                                    password As String,
                                                    persistent As Boolean) As Boolean
        Try
            ' Erst trennen falls vorhanden (ohne Fehler wenn nicht vorhanden)
            DisconnectNetworkDrive(driveLetter, True)

            ' NETRESOURCE-Struktur vorbereiten
            Dim netResource As New NETRESOURCE With {
                .dwType = RESOURCETYPE_DISK,
                .lpLocalName = driveLetter,
                .lpRemoteName = networkPath
            }

            Dim flags As Integer = If(persistent, CONNECT_UPDATE_PROFILE, 0)

            ' WICHTIG: Credentials als Nothing übergeben = Verwende aktuelle Windows-Credentials
            ' Wenn der Share öffentlich oder mit aktuellen Credentials erreichbar ist, funktioniert es
            Dim result As Integer = WNetAddConnection2(netResource, password, userName, flags)

            Select Case result
                Case ERROR_SUCCESS
                    _Logger.Debug($"✓ Laufwerk {driveLetter} erfolgreich gemappt")
                    Return True

                Case 1326 ' ERROR_LOGON_FAILURE
                    _Logger.Error($"❌ Authentifizierungsfehler (1326): Anmeldung fehlgeschlagen für [{networkPath}]")
                    _Logger.Error($"   → Der UNC-Pfad erfordert möglicherweise spezielle Credentials")
                    _Logger.Error($"   → Oder der aktuelle Benutzer hat keine Berechtigung")
                    Return False

                Case 53 ' ERROR_BAD_NETPATH
                    _Logger.Error($"❌ Netzwerkpfad nicht gefunden (53): [{networkPath}]")
                    Return False

                Case 67 ' ERROR_BAD_NET_NAME
                    _Logger.Error($"❌ Netzwerkname ungültig (67): [{networkPath}]")
                    Return False

                Case 85 ' ERROR_ALREADY_ASSIGNED
                    _Logger.Warn($"⚠️ Laufwerk {driveLetter} ist bereits zugewiesen (85)")
                    ' Versuche es zu trennen und erneut zu verbinden
                    DisconnectNetworkDrive(driveLetter, force:=True)
                    System.Threading.Thread.Sleep(500) ' Kurze Pause
                    result = WNetAddConnection2(netResource, password, userName, flags)
                    If result = ERROR_SUCCESS Then
                        _Logger.Info($"✓ Laufwerk {driveLetter} nach erneutem Versuch erfolgreich gemappt")
                        Return True
                    Else
                        Return False
                    End If

                Case Else
                    _Logger.Error($"❌ WNetAddConnection2 Error Code: {result}")
                    Return False
            End Select

        Catch ex As Exception
            _Logger.Error($"Fehler in MapNetworkDriveInternal: {ex.Message}")
            _Logger.Error(ex)
            Return False
        End Try
    End Function
    ''' <summary>
    ''' Test-Funktion um UNC-Pfad-Zugriff zu prüfen
    ''' </summary>
    Public Function TestUNCAccess(uncPath As String) As Boolean
        Try
            _Logger.Info($"🔍 Teste Zugriff auf UNC-Pfad: [{uncPath}]")

            ' Teste ob Pfad existiert und erreichbar ist
            If System.IO.Directory.Exists(uncPath) Then
                _Logger.Info($"✓ UNC-Pfad ist direkt erreichbar ohne Mapping")

                ' Teste Lese-Berechtigung
                Try
                    Dim files = System.IO.Directory.GetFiles(uncPath)
                    _Logger.Info($"✓ Lese-Berechtigung vorhanden ({files.Length} Dateien gefunden)")
                    Return True
                Catch permEx As UnauthorizedAccessException
                    _Logger.Error($"❌ Keine Lese-Berechtigung: {permEx.Message}")
                    Return False
                End Try
            Else
                _Logger.Error($"❌ UNC-Pfad nicht erreichbar oder existiert nicht")
                Return False
            End If

        Catch ex As Exception
            _Logger.Error($"❌ Fehler beim Testen des UNC-Zugriffs: {ex.Message}")
            _Logger.Error(ex)
            Return False
        End Try
    End Function
    ''' <summary>
    ''' Trennt ein Netzlaufwerk mit Windows-API
    ''' </summary>
    Public Function DisconnectNetworkDrive(driveLetter As String, Optional force As Boolean = True) As Boolean
        Try
            ' Formatierung sicherstellen
            driveLetter = driveLetter.Trim().ToUpper()
            If Not driveLetter.EndsWith(":") Then
                driveLetter &= ":"
            End If
            Dim driveExists As Boolean = False
            Try
                Dim driveInfo As New System.IO.DriveInfo(driveLetter)
                driveExists = driveInfo.IsReady
            Catch
                driveExists = False
            End Try

            If Not driveExists Then
                _Logger.Debug($"ℹ️ Laufwerk {driveLetter} existiert nicht - Disconnect übersprungen")
                Return True ' Kein Fehler, da das gewünschte Ergebnis erreicht ist (Laufwerk ist nicht verbunden)
            End If

            Dim flags As Integer = CONNECT_UPDATE_PROFILE
            Dim result As Integer = WNetCancelConnection2(driveLetter, flags, force)

            If result = ERROR_SUCCESS Then
                _Logger.Debug($"✓ Netzlaufwerk {driveLetter} erfolgreich getrennt")
                Return True
            ElseIf result = 2250 Then ' ERROR_NOT_CONNECTED
                ' Von WARN auf DEBUG herabgestuft, da es kein echter Fehler ist
                _Logger.Debug($"ℹ️ Laufwerk {driveLetter} war nicht verbunden (Code 2250)")
                Return True
            ElseIf result = ERROR_ALREADY_ASSIGNED Then
                _Logger.Debug($"ℹ️ Netzlaufwerk {driveLetter} war nicht verbunden")
                Return True
            Else
                _Logger.Warn($"⚠️ Warnung beim Trennen von {driveLetter}: Error Code {result}")
                Return False
            End If

        Catch ex As Exception
            _Logger.Debug($"⚠️ Fehler beim Trennen von {driveLetter} (ignoriert): {ex.Message}")
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Ermittelt alle gemappten Netzlaufwerke
    ''' </summary>
    Public Function GetMappedNetworkDrives() As List(Of NetworkDriveInfo)
        Dim mappedDrives As New List(Of NetworkDriveInfo)

        Try
            For Each drive As IO.DriveInfo In IO.DriveInfo.GetDrives()
                If drive.DriveType = IO.DriveType.Network Then
                    Dim driveInfo As New NetworkDriveInfo With {
                        .DriveLetter = drive.Name,
                        .NetworkPath = GetNetworkPath(drive.Name),
                        .DriveType = drive.DriveType,
                        .IsReady = drive.IsReady
                    }

                    If drive.IsReady Then
                        Try
                            driveInfo.TotalSize = drive.TotalSize
                            driveInfo.FreeSpace = drive.AvailableFreeSpace
                        Catch ex As Exception
                            _Logger.Debug($"Konnte Größeninformationen für {drive.Name} nicht ermitteln: {ex.Message}")
                        End Try
                    End If

                    mappedDrives.Add(driveInfo)
                End If
            Next

            _Logger.Debug($"Insgesamt {mappedDrives.Count} Netzlaufwerk(e) gefunden")
            Return mappedDrives

        Catch ex As Exception
            _Logger.Error($"Fehler beim Ermitteln der Netzlaufwerke: {ex.Message}")
            _Logger.Error(ex)
            Return mappedDrives
        End Try
    End Function

    ''' <summary>
    ''' Ermittelt den UNC-Pfad eines gemappten Laufwerks
    ''' </summary>
    Public Function GetNetworkPath(driveLetter As String) As String
        Try
            driveLetter = driveLetter.Trim().ToUpper()
            If Not driveLetter.EndsWith(":") Then
                driveLetter &= ":"
            End If

            Dim network As Object = CreateObject("WScript.Network")
            Dim enumDrives As Object = network.EnumNetworkDrives()

            For i As Integer = 0 To enumDrives.Count - 1 Step 2
                If enumDrives.Item(i).ToString.Equals(driveLetter, StringComparison.OrdinalIgnoreCase) Then
                    Return enumDrives.Item(i + 1).ToString()
                End If
            Next

            Return String.Empty

        Catch ex As Exception
            _Logger.Debug($"Fehler beim Ermitteln des Netzwerkpfads für {driveLetter}: {ex.Message}")
            Return String.Empty
        End Try
    End Function

    ''' <summary>
    ''' Prüft ob ein bestimmtes Laufwerk als Netzlaufwerk gemappt ist
    ''' </summary>
    Public Shared Function IsDriveMapped(driveLetter As String) As Boolean
        Try
            driveLetter = driveLetter.Trim().ToUpper()
            If Not driveLetter.EndsWith(":") Then
                driveLetter &= ":"
            End If
            If Not driveLetter.EndsWith("\") Then
                driveLetter &= "\"
            End If

            Dim driveInfo As New IO.DriveInfo(driveLetter)
            Return driveInfo.DriveType = IO.DriveType.Network AndAlso driveInfo.IsReady

        Catch ex As Exception
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Gibt eine formatierte Übersicht aller gemappten Netzlaufwerke zurück
    ''' </summary>
    Public Function GetMappedDrivesInfo() As String
        Dim result As New System.Text.StringBuilder()
        Dim drives = GetMappedNetworkDrives()

        If drives.Count = 0 Then
            Return "Keine Netzlaufwerke gefunden."
        End If

        result.AppendLine($"Gemappte Netzlaufwerke ({drives.Count}):")
        result.AppendLine(New String("-"c, 60))

        For Each drive In drives
            result.AppendLine($"Laufwerk: {drive.DriveLetter}")
            result.AppendLine($"  Pfad: {drive.NetworkPath}")
            result.AppendLine($"  Status: {If(drive.IsReady, "Verfügbar", "Nicht verfügbar")}")

            If drive.IsReady AndAlso drive.TotalSize > 0 Then
                Dim totalGB As Double = drive.TotalSize / (1024.0 ^ 3)
                Dim freeGB As Double = drive.FreeSpace / (1024.0 ^ 3)
                result.AppendLine($"  Größe: {totalGB:N2} GB (Frei: {freeGB:N2} GB)")
            End If

            result.AppendLine()
        Next

        Return result.ToString()
    End Function
    ''' <summary>
    ''' Mappt ein spezifisches Laufwerk (z.B. "V") mit Blacklist-Prüfung
    ''' </summary>
    Public Function MapSpecificDrive(driveLetter As String, blacklist As String, networkPath As String) As Boolean
        Try
            ' Formatierung sicherstellen
            driveLetter = driveLetter.Trim().ToUpper().Replace(":", "")

            If String.IsNullOrEmpty(driveLetter) OrElse driveLetter.Length <> 1 Then
                _Logger.Warn($"⚠️ Ungültiger Laufwerksbuchstabe: [{driveLetter}]")
                Return False
            End If

            Dim driveWithColon As String = driveLetter & ":"

            ' Prüfen ob Laufwerk verfügbar ist
            If Not IsDriveLetterAvailable(driveWithColon, blacklist) Then
                _Logger.Warn($"⚠️ Laufwerk {driveWithColon} ist nicht verfügbar (bereits verwendet oder in Blacklist)")
                Return False
            End If

            ' UNC-Pfad vorbereiten (ohne abschließenden Backslash)
            Dim uncPath As String = networkPath.TrimEnd("\"c)

            ' Laufwerk mappen (OHNE Credentials, persistent=False für temporäres Mapping)
            Dim result = MapNetworkDrive(driveWithColon, uncPath, blacklist, userName:=Nothing, password:=Nothing, persistent:=False)

            If Not String.IsNullOrEmpty(result) Then
                _Logger.Debug($"✓ Laufwerk {driveWithColon} erfolgreich gemappt zu [{uncPath}]")
                Return True
            Else
                _Logger.Error($"❌ Fehler beim Mappen von {driveWithColon}")
                Return False
            End If

        Catch ex As Exception
            _Logger.Error($"Fehler in MapSpecificDrive: {ex.Message}")
            _Logger.Error(ex)
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Mappt automatisch den nächsten freien Laufwerksbuchstaben (Z→A)
    ''' </summary>
    Public Function MapDriveAutomatic(blacklist As String, networkPath As String) As String
        Try
            ' UNC-Pfad vorbereiten (ohne abschließenden Backslash)
            Dim uncPath As String = networkPath.TrimEnd("\"c)

            _Logger.Debug($"🔍 Suche automatisch freien Laufwerksbuchstaben...")
            _Logger.Debug($"   Blacklist: [{blacklist}]")
            _Logger.Debug($"   Netzwerkpfad: [{uncPath}]")

            ' Automatisches Mapping (leer = automatische Auswahl, persistent=False)
            Dim result = MapNetworkDrive("", uncPath, blacklist, userName:=Nothing, password:=Nothing, persistent:=False)

            If Not String.IsNullOrEmpty(result) Then
                _Logger.Debug($"✓ Automatisch gewähltes Laufwerk: {result}")
                Return result
            Else
                _Logger.Error($"❌ Kein freier Laufwerksbuchstabe verfügbar")
                Return String.Empty
            End If

        Catch ex As Exception
            _Logger.Error($"Fehler in MapDriveAutomatic: {ex.Message}")
            _Logger.Error(ex)
            Return String.Empty
        End Try
    End Function

End Class
