using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using System.Text.RegularExpressions;
namespace Quadarax.Foundation.Core.IO.FileSystem.Memory
{


    public class MemoryDirectory : IDirectory
    {
        private readonly MemoryFileSystem _fileSystem;

        public IFileSystem FileSystem => _fileSystem;

        public MemoryDirectory(MemoryFileSystem fileSystem)
        {
            _fileSystem = fileSystem;
        }

        public IDirectoryInfo CreateDirectory(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentException("Path cannot be null or whitespace.", nameof(path));
            }

            // Normalize the path
            string normalizedPath = _fileSystem.Path.GetFullPath(path);

            // Split the path into its components
            string[] pathParts = normalizedPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

            string? currentPath = _fileSystem.Path.GetPathRoot(normalizedPath);
            MemoryDirectoryEntry? currentDir = null;

            // Ensure the root directory exists
            if (!_fileSystem.FileSystemDictionary.TryGetValue(currentPath ?? string.Empty, out var rootEntry))
            {
                rootEntry = new MemoryDirectoryEntry
                {
                    Name = currentPath ?? string.Empty,
                    FullName = currentPath ?? string.Empty,
                    Attributes = FileAttributes.Directory,
                    CreationTime = DateTime.Now,
                    LastAccessTime = DateTime.Now,
                    LastWriteTime = DateTime.Now
                };
                _fileSystem.FileSystemDictionary[currentPath ?? string.Empty] = rootEntry;
            }
            currentDir = (MemoryDirectoryEntry)rootEntry;

            // Create or navigate through each directory in the path
            foreach (string part in pathParts.Skip(1)) // Skip the root
            {
                currentPath = Path.Combine(currentPath ?? string.Empty, part);

                if (_fileSystem.FileSystemDictionary.TryGetValue(currentPath, out var existingEntry))
                {
                    if (existingEntry is MemoryFileEntry)
                    {
                        throw new IOException($"Cannot create directory '{currentPath}': A file with the same name already exists.");
                    }
                    currentDir = (MemoryDirectoryEntry)existingEntry;
                }
                else
                {
                    var newDir = new MemoryDirectoryEntry
                    {
                        Name = part,
                        FullName = currentPath,
                        Attributes = FileAttributes.Directory,
                        CreationTime = DateTime.Now,
                        LastAccessTime = DateTime.Now,
                        LastWriteTime = DateTime.Now
                    };
                    _fileSystem.FileSystemDictionary[currentPath] = newDir;
                    currentDir.Children[part] = newDir;
                    currentDir = newDir;
                }
            }

            // Update the last access time of the final directory
            currentDir.LastAccessTime = DateTime.Now;

            // Return an IDirectoryInfo object for the created or existing directory
            return new MemoryDirectoryInfo(_fileSystem, normalizedPath);
        }

        public void Delete(string path)
        {
            Delete(path, false);
        }

        public void Delete(string path, bool recursive)
        {
            var normalizedPath = NormalizePath(path);
            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) || !(entry is MemoryDirectoryEntry dirEntry))
            {
                throw new DirectoryNotFoundException($"Directory not found: {normalizedPath}");
            }

            if (!recursive && dirEntry.Children.Count > 0)
            {
                throw new IOException($"The directory '{normalizedPath}' is not empty.");
            }

            var keysToRemove = _fileSystem.FileSystemDictionary.Keys
                .Where(k => k.StartsWith(normalizedPath))
                .ToList();

            foreach (var key in keysToRemove)
            {
                _fileSystem.FileSystemDictionary.Remove(key);
            }
        }

        public IEnumerable<string> EnumerateDirectories(string path)
        {
            return EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly);
        }

        public IEnumerable<string> EnumerateDirectories(string path, string searchPattern)
        {
            return EnumerateDirectories(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        public IEnumerable<string> EnumerateDirectories(string path, string searchPattern, SearchOption searchOption)
        {
            var normalizedPath = NormalizePath(path);
            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) || !(entry is MemoryDirectoryEntry dirEntry))
            {
                throw new DirectoryNotFoundException($"Directory not found: {normalizedPath}");
            }

            var directories = new List<string>();
            EnumerateDirectoriesRecursive(dirEntry, searchPattern, searchOption, directories);
            return directories;
        }

        private void EnumerateDirectoriesRecursive(MemoryDirectoryEntry directory, string searchPattern, SearchOption searchOption, List<string> result)
        {
            foreach (var child in directory.Children.Values)
            {
                if (child is MemoryDirectoryEntry childDir && MatchesPattern(child.Name, searchPattern))
                {
                    result.Add(child.FullName);
                    if (searchOption == SearchOption.AllDirectories)
                    {
                        EnumerateDirectoriesRecursive(childDir, searchPattern, searchOption, result);
                    }
                }
            }
        }

        public IEnumerable<string> EnumerateFiles(string path)
        {
            return EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly);
        }

        public IEnumerable<string> EnumerateFiles(string path, string searchPattern)
        {
            return EnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        public IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOption)
        {
            var normalizedPath = NormalizePath(path);
            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) || !(entry is MemoryDirectoryEntry dirEntry))
            {
                throw new DirectoryNotFoundException($"Directory not found: {normalizedPath}");
            }

            var files = new List<string>();
            EnumerateFilesRecursive(dirEntry, searchPattern, searchOption, files);
            return files;
        }

        private void EnumerateFilesRecursive(MemoryDirectoryEntry directory, string searchPattern, SearchOption searchOption, List<string> result)
        {
            foreach (var child in directory.Children.Values)
            {
                if (child is MemoryFileEntry && MatchesPattern(child.Name, searchPattern))
                {
                    result.Add(child.FullName);
                }
                else if (child is MemoryDirectoryEntry childDir && searchOption == SearchOption.AllDirectories)
                {
                    EnumerateFilesRecursive(childDir, searchPattern, searchOption, result);
                }
            }
        }

        public IEnumerable<string> EnumerateFileSystemEntries(string path)
        {
            return EnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly);
        }

        public IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern)
        {
            return EnumerateFileSystemEntries(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        public IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
        {
            var normalizedPath = NormalizePath(path);
            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) || !(entry is MemoryDirectoryEntry dirEntry))
            {
                throw new DirectoryNotFoundException($"Directory not found: {normalizedPath}");
            }

            var entries = new List<string>();
            EnumerateFileSystemEntriesRecursive(dirEntry, searchPattern, searchOption, entries);
            return entries;
        }

        private void EnumerateFileSystemEntriesRecursive(MemoryDirectoryEntry directory, string searchPattern, SearchOption searchOption, List<string> result)
        {
            foreach (var child in directory.Children.Values)
            {
                if (MatchesPattern(child.Name, searchPattern))
                {
                    result.Add(child.FullName);
                }

                if (child is MemoryDirectoryEntry childDir && searchOption == SearchOption.AllDirectories)
                {
                    EnumerateFileSystemEntriesRecursive(childDir, searchPattern, searchOption, result);
                }
            }
        }

        public bool Exists(string? path)
        {
            var normalizedPath = NormalizePath(path);
            return _fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) && entry is MemoryDirectoryEntry;
        }

        public string[] GetDirectories(string path)
        {
            return GetDirectories(path, "*", SearchOption.TopDirectoryOnly);
        }

        public string[] GetDirectories(string path, string searchPattern)
        {
            return GetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        public string[] GetDirectories(string path, string searchPattern, SearchOption searchOption)
        {
            return EnumerateDirectories(path, searchPattern, searchOption).ToArray();
        }

        public string[] GetFiles(string path)
        {
            return GetFiles(path, "*", SearchOption.TopDirectoryOnly);
        }

        public string[] GetFiles(string path, string searchPattern)
        {
            return GetFiles(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        public string[] GetFiles(string path, string searchPattern, SearchOption searchOption)
        {
            return EnumerateFiles(path, searchPattern, searchOption).ToArray();
        }

        public string[] GetFileSystemEntries(string path)
        {
            return GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly);
        }

        public string[] GetFileSystemEntries(string path, string searchPattern)
        {
            return GetFileSystemEntries(path, searchPattern, SearchOption.TopDirectoryOnly);
        }

        public string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption)
        {
            return EnumerateFileSystemEntries(path, searchPattern, searchOption).ToArray();
        }

        public void Move(string sourceDirName, string destDirName)
        {
            var normalizedSourcePath = NormalizePath(sourceDirName);
            var normalizedDestPath = NormalizePath(destDirName);

            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedSourcePath, out var sourceEntry) || !(sourceEntry is MemoryDirectoryEntry sourceDir))
            {
                throw new DirectoryNotFoundException($"Source directory not found: {normalizedSourcePath}");
            }

            if (_fileSystem.FileSystemDictionary.ContainsKey(normalizedDestPath))
            {
                throw new IOException($"Cannot create '{normalizedDestPath}' because a file or directory with the same name already exists.");
            }

            var entriesToMove = _fileSystem.FileSystemDictionary
                .Where(kvp => kvp.Key.StartsWith(normalizedSourcePath))
                .ToList();

            foreach (var entry in entriesToMove)
            {
                var newPath = entry.Key.Replace(normalizedSourcePath, normalizedDestPath);
                _fileSystem.FileSystemDictionary[newPath] = entry.Value;
                entry.Value.FullName = newPath;
                _fileSystem.FileSystemDictionary.Remove(entry.Key);
            }
        }

        private string NormalizePath(string? path)
        {
            if (path == null)
                return string.Empty;
            return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        }

        private bool MatchesPattern(string name, string pattern)
        {
            return System.Text.RegularExpressions.Regex.IsMatch(name, "^" + System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*").Replace("\\?", ".") + "$");
        }

        public IEnumerable<string> EnumerateFileSystemEntries(string path, string searchPattern, EnumerationOptions enumerationOptions)
        {
            var normalizedPath = NormalizePath(path);
            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) || !(entry is MemoryDirectoryEntry dirEntry))
            {
                throw new DirectoryNotFoundException($"Directory not found: {normalizedPath}");
            }

            var entries = new List<string>();
            EnumerateFileSystemEntriesRecursive(dirEntry, searchPattern, enumerationOptions, entries);
            return entries;
        }

        private void EnumerateFileSystemEntriesRecursive(MemoryDirectoryEntry directory, string searchPattern, EnumerationOptions options, List<string> result)
        {
            foreach (var child in directory.Children.Values)
            {
                if (ShouldIncludeEntry(child, searchPattern, options))
                {
                    result.Add(child.FullName);
                }

                if (child is MemoryDirectoryEntry childDir && options.RecurseSubdirectories)
                {
                    EnumerateFileSystemEntriesRecursive(childDir, searchPattern, options, result);
                }
            }

            if (options.ReturnSpecialDirectories)
            {
                if (MatchesPattern(".", searchPattern, options) && !result.Contains(directory.FullName))
                {
                    result.Add(directory.FullName);
                }

                var parentDir = Path.GetDirectoryName(directory.FullName);
                if (parentDir != null && MatchesPattern("..", searchPattern, options) && !result.Contains(parentDir))
                {
                    result.Add(parentDir);
                }
            }
        }

        private bool ShouldIncludeEntry(MemoryFileSystemEntry entry, string searchPattern, EnumerationOptions options)
        {
            if ((entry.Attributes & options.AttributesToSkip) != 0)
            {
                return false;
            }

            if (options.IgnoreInaccessible)
            {
                // Simulate inaccessible entries (e.g., by checking a hypothetical 'IsAccessible' property)
                // For this example, we'll assume all entries are accessible
            }

            return MatchesPattern(entry.Name, searchPattern, options);
        }
        public IEnumerable<string> EnumerateFiles(string path, string searchPattern, EnumerationOptions enumerationOptions)
        {
            var normalizedPath = NormalizePath(path);
            if (!_fileSystem.FileSystemDictionary.TryGetValue(normalizedPath, out var entry) || !(entry is MemoryDirectoryEntry dirEntry))
            {
                throw new DirectoryNotFoundException($"Directory not found: {normalizedPath}");
            }

            var files = new List<string>();
            EnumerateFilesRecursive(dirEntry, searchPattern, enumerationOptions, files);
            return files;
        }

        private void EnumerateFilesRecursive(MemoryDirectoryEntry directory, string searchPattern, EnumerationOptions options, List<string> result)
        {
            foreach (var child in directory.Children.Values)
            {
                if (child is MemoryFileEntry fileEntry)
                {
                    if (ShouldIncludeFile(fileEntry, searchPattern, options))
                    {
                        result.Add(child.FullName);
                    }
                }
                else if (child is MemoryDirectoryEntry childDir && options.RecurseSubdirectories)
                {
                    EnumerateFilesRecursive(childDir, searchPattern, options, result);
                }
            }
        }

        private bool ShouldIncludeFile(MemoryFileEntry file, string searchPattern, EnumerationOptions options)
        {
            if ((file.Attributes & options.AttributesToSkip) != 0)
            {
                return false;
            }

            if (options.IgnoreInaccessible)
            {
                // Simulate inaccessible files (e.g., by checking a hypothetical 'IsAccessible' property)
                // For this example, we'll assume all files are accessible
            }

            return MatchesPattern(file.Name, searchPattern, options);
        }



        private bool MatchesWin32(string name, string pattern, MatchCasing matchCasing)
        {
            string regexPattern = "^" + Regex.Escape(pattern)
                .Replace("\\*", ".*")
                .Replace("\\?", ".")
                + "$";

            RegexOptions regexOptions = matchCasing == MatchCasing.CaseInsensitive
                ? RegexOptions.IgnoreCase
                : RegexOptions.None;

            return Regex.IsMatch(name, regexPattern, regexOptions);
        }

        private bool MatchesSimple(string name, string pattern, MatchCasing matchCasing)
        {
            return matchCasing == MatchCasing.CaseInsensitive
                ? name.Equals(pattern, StringComparison.OrdinalIgnoreCase)
                : name.Equals(pattern, StringComparison.Ordinal);
        }

        private bool MatchesRegex(string name, string pattern, MatchCasing matchCasing)
        {
            RegexOptions regexOptions = matchCasing == MatchCasing.CaseInsensitive
                ? RegexOptions.IgnoreCase
                : RegexOptions.None;

            return Regex.IsMatch(name, pattern, regexOptions);
        }
        private bool MatchesPattern(string name, string pattern, EnumerationOptions options)
        {
            if (options.MatchType == MatchType.Win32)
            {
                return MatchesWin32(name, pattern, options.MatchCasing);
            }
            else if (options.MatchType == MatchType.Simple)
            {
                return MatchesSimple(name, pattern, options.MatchCasing);
            }
            else // MatchType.Regular
            {
                return MatchesRegex(name, pattern, options.MatchCasing);
            }
        }


        public IDirectoryInfo CreateDirectory(string path, UnixFileMode unixCreateMode)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentException("Path cannot be null or whitespace.", nameof(path));
            }

            // Normalize the path
            string normalizedPath = _fileSystem.Path.GetFullPath(path);

            // Split the path into its components
            string[] pathParts = normalizedPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);

            string? currentPath = _fileSystem.Path.GetPathRoot(normalizedPath);
            MemoryDirectoryEntry? currentDir = null;

            // Ensure the root directory exists
            if (!_fileSystem.FileSystemDictionary.TryGetValue(currentPath ?? string.Empty, out var rootEntry))
            {
                rootEntry = new MemoryDirectoryEntry
                {
                    Name = currentPath ?? string.Empty,
                    FullName = currentPath ?? string.Empty,
                    Attributes = FileAttributes.Directory,
                    CreationTime = DateTime.Now,
                    LastAccessTime = DateTime.Now,
                    LastWriteTime = DateTime.Now,
                    UnixFileMode = System.IO.UnixFileMode.None
                };
                _fileSystem.FileSystemDictionary[currentPath ?? string.Empty] = rootEntry;
            }
            currentDir = (MemoryDirectoryEntry)rootEntry;

            // Create or navigate through each directory in the path
            foreach (string part in pathParts.Skip(1)) // Skip the root
            {
                currentPath = Path.Combine(currentPath ?? string.Empty, part);

                if (_fileSystem.FileSystemDictionary.TryGetValue(currentPath, out var existingEntry))
                {
                    if (existingEntry is MemoryFileEntry)
                    {
                        throw new IOException($"Cannot create directory '{currentPath}': A file with the same name already exists.");
                    }
                    currentDir = (MemoryDirectoryEntry)existingEntry;
                }
                else
                {
                    var newDir = new MemoryDirectoryEntry
                    {
                        Name = part,
                        FullName = currentPath,
                        Attributes = FileAttributes.Directory,
                        CreationTime = DateTime.Now,
                        LastAccessTime = DateTime.Now,
                        LastWriteTime = DateTime.Now,
                        UnixFileMode = unixCreateMode
                    };
                    _fileSystem.FileSystemDictionary[currentPath] = newDir;
                    currentDir.Children[part] = newDir;
                    currentDir = newDir;
                }
            }

            // Update the last access time of the final directory
            currentDir.LastAccessTime = DateTime.Now;

            // Return an IDirectoryInfo object for the created or existing directory
            return new MemoryDirectoryInfo(_fileSystem, normalizedPath);
        }

        public IFileSystemInfo CreateSymbolicLink(string path, string pathToTarget)
        {
            if (string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentException("Path cannot be null or whitespace.", nameof(path));
            }

            if (string.IsNullOrWhiteSpace(pathToTarget))
            {
                throw new ArgumentException("Target path cannot be null or whitespace.", nameof(pathToTarget));
            }

            // Normalize the paths
            string normalizedPath = _fileSystem.Path.GetFullPath(path);
            string normalizedTargetPath = _fileSystem.Path.GetFullPath(pathToTarget);

            // Check if the symlink already exists
            if (_fileSystem.FileSystemDictionary.ContainsKey(normalizedPath))
            {
                throw new IOException($"Cannot create '{normalizedPath}': A file or directory with the same name already exists.");
            }

            // Determine if the target is a file or directory
            bool isTargetDirectory = _fileSystem.Directory.Exists(normalizedTargetPath);

            // Create the symbolic link entry
            MemoryFileSystemEntry symlinkEntry;
            if (isTargetDirectory)
            {
                symlinkEntry = new MemoryDirectoryEntry
                {
                    Name = _fileSystem.Path.GetFileName(normalizedPath),
                    FullName = normalizedPath,
                    Attributes = FileAttributes.Directory | FileAttributes.ReparsePoint,
                    CreationTime = DateTime.Now,
                    LastAccessTime = DateTime.Now,
                    LastWriteTime = DateTime.Now,
                    IsSymbolicLink = true,
                    SymbolicLinkTarget = normalizedTargetPath
                };
            }
            else
            {
                symlinkEntry = new MemoryFileEntry
                {
                    Name = _fileSystem.Path.GetFileName(normalizedPath),
                    FullName = normalizedPath,
                    Attributes = FileAttributes.ReparsePoint,
                    CreationTime = DateTime.Now,
                    LastAccessTime = DateTime.Now,
                    LastWriteTime = DateTime.Now,
                    IsSymbolicLink = true,
                    SymbolicLinkTarget = normalizedTargetPath
                };
            }

            // Add the symlink to the file system
            _fileSystem.FileSystemDictionary[normalizedPath] = symlinkEntry;

            // Add the symlink to its parent directory's children
            string? parentPath = _fileSystem.Path.GetDirectoryName(normalizedPath);
            if (_fileSystem.FileSystemDictionary.TryGetValue(parentPath ?? string.Empty, out var parentEntry) && parentEntry is MemoryDirectoryEntry parentDir)
            {
                parentDir.Children[symlinkEntry.Name] = symlinkEntry;
            }

            // Return the appropriate IFileSystemInfo object
            return isTargetDirectory
                ? (IFileSystemInfo)new MemoryDirectoryInfo(_fileSystem, normalizedPath)
                : new MemoryFileInfo(_fileSystem, normalizedPath);
        }

        public IDirectoryInfo CreateTempSubdirectory(string? prefix = null)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<string> EnumerateDirectories(string path, string searchPattern, EnumerationOptions enumerationOptions)
        {
            throw new NotImplementedException();
        }


        public DateTime GetCreationTime(string path)
        {
            throw new NotImplementedException();
        }

        public DateTime GetCreationTimeUtc(string path)
        {
            throw new NotImplementedException();
        }

        public string GetCurrentDirectory()
        {
            throw new NotImplementedException();
        }

        public string[] GetDirectories(string path, string searchPattern, EnumerationOptions enumerationOptions)
        {
            throw new NotImplementedException();
        }

        public string GetDirectoryRoot(string path)
        {
            throw new NotImplementedException();
        }

        public string[] GetFiles(string path, string searchPattern, EnumerationOptions enumerationOptions)
        {
            throw new NotImplementedException();
        }

        public string[] GetFileSystemEntries(string path, string searchPattern, EnumerationOptions enumerationOptions)
        {
            throw new NotImplementedException();
        }

        public DateTime GetLastAccessTime(string path)
        {
            throw new NotImplementedException();
        }

        public DateTime GetLastAccessTimeUtc(string path)
        {
            throw new NotImplementedException();
        }

        public DateTime GetLastWriteTime(string path)
        {
            throw new NotImplementedException();
        }

        public DateTime GetLastWriteTimeUtc(string path)
        {
            throw new NotImplementedException();
        }

        public string[] GetLogicalDrives()
        {
            throw new NotImplementedException();
        }

        public IDirectoryInfo? GetParent(string path)
        {
            throw new NotImplementedException();
        }

        public IFileSystemInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget)
        {
            throw new NotImplementedException();
        }

        public void SetCreationTime(string path, DateTime creationTime)
        {
            throw new NotImplementedException();
        }

        public void SetCreationTimeUtc(string path, DateTime creationTimeUtc)
        {
            throw new NotImplementedException();
        }

        public void SetCurrentDirectory(string path)
        {
            throw new NotImplementedException();
        }

        public void SetLastAccessTime(string path, DateTime lastAccessTime)
        {
            throw new NotImplementedException();
        }

        public void SetLastAccessTimeUtc(string path, DateTime lastAccessTimeUtc)
        {
            throw new NotImplementedException();
        }

        public void SetLastWriteTime(string path, DateTime lastWriteTime)
        {
            throw new NotImplementedException();
        }

        public void SetLastWriteTimeUtc(string path, DateTime lastWriteTimeUtc)
        {
            throw new NotImplementedException();
        }

        // Implement other methods (e.g., GetParent, GetDirectoryRoot) as needed
    }
}