using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AIRuntime.Runtime.Filesystem.Models;
using System.Text;
using System.Security.Cryptography;

namespace AIRuntime.Runtime.Filesystem
{
    /// <summary>
    /// Default filesystem implementation.
    /// All filesystem operations are constrained to the configured workspace.
    /// </summary>
    public sealed class LocalFileSystem : IFileSystem, IAtomicFileSystem
    {
        private readonly Workspace _workspace;

        public LocalFileSystem(Workspace workspace)
        {
            _workspace = workspace ??
                throw new ArgumentNullException(nameof(workspace));
        }

        //==================================================
        // Helpers
        //==================================================

        private string Resolve(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                throw new ArgumentException(
                    "Path cannot be empty.",
                    nameof(path));

            return _workspace.Resolve(path);
        }

        private static void EnsureParentDirectory(string fullPath)
        {
            string directory = Path.GetDirectoryName(fullPath);

            if (!string.IsNullOrWhiteSpace(directory))
                Directory.CreateDirectory(directory);
        }
        public Task ValidatePathAsync(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                throw new ArgumentException(
                    "Path cannot be null or empty.",
                    nameof(path));

            // Resolve() performs all workspace validation.
            _workspace.Resolve(path);

            return Task.CompletedTask;
        }
        //==================================================
        // File Operations
        //==================================================

        public Task<bool> FileExistsAsync(string path)
        {
            return Task.FromResult(
                File.Exists(Resolve(path)));
        }

        public async Task<string> ReadFileAsync(string path)
        {
            string fullPath = Resolve(path);

            if (!File.Exists(fullPath))
                throw new FileNotFoundException(
                    $"File not found: {path}");

            return await File.ReadAllTextAsync(fullPath);
        }

        public async Task WriteFileAsync(
            string path,
            string content)
        {
            string resolvedPath =
                _workspace.Resolve(path);

            string directory =
                Path.GetDirectoryName(resolvedPath);

            if (!string.IsNullOrWhiteSpace(directory))
            {
                Directory.CreateDirectory(directory);
            }

            await File.WriteAllTextAsync(
                resolvedPath,
                content ?? string.Empty,
                new UTF8Encoding(false));
        }

        public async Task AppendFileAsync(
            string path,
            string contents)
        {
            string fullPath = Resolve(path);

            EnsureParentDirectory(fullPath);

            string appendContents =
                contents ??
                string.Empty;


            if (File.Exists(
                    fullPath) &&
                !string.IsNullOrEmpty(
                    appendContents))
            {
                string existingContents =
                    await File.ReadAllTextAsync(
                        fullPath);


                if (!string.IsNullOrEmpty(
                        existingContents) &&
                    !EndsWithNewLine(
                        existingContents) &&
                    !StartsWithNewLine(
                        appendContents))
                {
                    appendContents =
                        DetectNewLine(
                            existingContents) +
                        appendContents;
                }
            }


            await File.AppendAllTextAsync(
                fullPath,
                appendContents);
        }

        public async Task<AtomicFileWriteResult> WriteFileAtomicallyAsync(
            string path,
            string contents,
            string expectedSha256,
            bool expectedAbsent,
            bool allowOverwrite)
        {
            string resolvedPath = _workspace.Resolve(path);
            string directory = Path.GetDirectoryName(resolvedPath);
            if (!string.IsNullOrWhiteSpace(directory)) Directory.CreateDirectory(directory);

            bool existed = File.Exists(resolvedPath);
            string previous = existed
                ? await File.ReadAllTextAsync(resolvedPath)
                : string.Empty;
            string previousHash = existed ? ComputeSha256(previous) : string.Empty;

            if (expectedAbsent && existed)
                throw new IOException($"File '{path}' already exists; ExpectedAbsent was true.");
            if (existed && !allowOverwrite && string.IsNullOrWhiteSpace(expectedSha256))
                throw new IOException($"File '{path}' already exists and overwrite was not authorized.");
            if (!string.IsNullOrWhiteSpace(expectedSha256) &&
                (!existed || !string.Equals(previousHash, expectedSha256.Trim(), StringComparison.OrdinalIgnoreCase)))
                throw new IOException($"File '{path}' changed or is absent; expected SHA-256 '{expectedSha256}', current '{previousHash}'.");

            string value = contents ?? string.Empty;
            string temporary = resolvedPath + ".airuntime-" + Guid.NewGuid().ToString("N") + ".tmp";
            try
            {
                await File.WriteAllTextAsync(temporary, value, new UTF8Encoding(false));
                if (existed)
                    File.Replace(temporary, resolvedPath, null);
                else
                    File.Move(temporary, resolvedPath);
            }
            finally
            {
                if (File.Exists(temporary)) File.Delete(temporary);
            }

            return new AtomicFileWriteResult
            {
                Created = !existed,
                Replaced = existed,
                PreviousSha256 = previousHash,
                CurrentSha256 = ComputeSha256(value),
                PreviousLength = previous.Length,
                CurrentLength = value.Length
            };
        }

        private static string ComputeSha256(string value)
        {
            using (SHA256 algorithm = SHA256.Create())
            {
                byte[] hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty));
                return string.Concat(hash.Select(item => item.ToString("x2")));
            }
        }


        private static bool StartsWithNewLine(
            string value)
        {
            return value[0] ==
                   '\r' ||
                   value[0] ==
                   '\n';
        }


        private static bool EndsWithNewLine(
            string value)
        {
            char lastCharacter =
                value[value.Length -
                      1];

            return lastCharacter ==
                   '\r' ||
                   lastCharacter ==
                   '\n';
        }


        private static string DetectNewLine(
            string value)
        {
            return value.IndexOf(
                       "\r\n",
                       StringComparison.Ordinal) >=
                   0
                ? "\r\n"
                : "\n";
        }

        public Task DeleteFileAsync(string path)
        {
            string fullPath = Resolve(path);

            if (File.Exists(fullPath))
                File.Delete(fullPath);

            return Task.CompletedTask;
        }

        public Task MoveFileAsync(
            string source,
            string destination)
        {
            string sourcePath = Resolve(source);
            string destinationPath = Resolve(destination);

            EnsureDistinctPaths(
                sourcePath,
                destinationPath);

            if (!File.Exists(sourcePath))
                throw new FileNotFoundException(
                    $"File not found: {source}");

            EnsureParentDirectory(destinationPath);

            if (File.Exists(destinationPath))
            {
                File.Replace(
                    sourcePath,
                    destinationPath,
                    destinationBackupFileName: null);
            }
            else
            {
                File.Move(
                    sourcePath,
                    destinationPath);
            }

            return Task.CompletedTask;
        }

        public Task CopyFileAsync(
            string source,
            string destination)
        {
            string sourcePath = Resolve(source);
            string destinationPath = Resolve(destination);

            EnsureDistinctPaths(
                sourcePath,
                destinationPath);

            if (!File.Exists(sourcePath))
                throw new FileNotFoundException(
                    $"File not found: {source}");

            EnsureParentDirectory(destinationPath);

            File.Copy(
                sourcePath,
                destinationPath,
                overwrite: true);

            return Task.CompletedTask;
        }

        //==================================================
        // Directory Operations
        //==================================================

        public Task<bool> DirectoryExistsAsync(string path)
        {
            return Task.FromResult(
                Directory.Exists(Resolve(path)));
        }

        public Task CreateDirectoryAsync(string path)
        {
            Directory.CreateDirectory(
                Resolve(path));

            return Task.CompletedTask;
        }

        public Task DeleteDirectoryAsync(
            string path,
            bool recursive)
        {
            string fullPath = Resolve(path);

            if (_workspace.IsWorkspaceRoot(fullPath))
            {
                throw new InvalidOperationException(
                    "The workspace root directory cannot be deleted.");
            }

            if (Directory.Exists(fullPath))
                Directory.Delete(fullPath, recursive);

            return Task.CompletedTask;
        }

        public Task<IReadOnlyList<string>> ListDirectoryAsync(
            string path)
        {
            string fullPath = Resolve(path);

            if (!Directory.Exists(fullPath))
                throw new DirectoryNotFoundException(
                    $"Directory not found: {path}");

            IReadOnlyList<string> entries = Directory
                .EnumerateFileSystemEntries(fullPath)
                .Select(Path.GetFileName)
                .Where(x => x != null)
                .Cast<string>()
                .OrderBy(
                    entry => entry,
                    StringComparer.OrdinalIgnoreCase)
                .ToList();

            return Task.FromResult(entries);
        }

        //==================================================
        // Information
        //==================================================

        public Task<FileInfoModel> GetFileInfoAsync(string path)
        {
            string fullPath = Resolve(path);

            if (File.Exists(fullPath))
            {
                FileInfo info = new(fullPath);

                return Task.FromResult(new FileInfoModel
                {
                    Exists = true,
                    IsDirectory = false,
                    IsSymbolicLink =
                        (info.Attributes &
                         FileAttributes.ReparsePoint) !=
                        0,
                    Path = path,
                    Name = info.Name,
                    Extension = info.Extension,
                    Size = info.Length,
                    CreatedUtc = info.CreationTimeUtc,
                    ModifiedUtc = info.LastWriteTimeUtc
                });
            }

            if (Directory.Exists(fullPath))
            {
                DirectoryInfo info = new(fullPath);

                return Task.FromResult(new FileInfoModel
                {
                    Exists = true,
                    IsDirectory = true,
                    IsSymbolicLink =
                        (info.Attributes &
                         FileAttributes.ReparsePoint) !=
                        0,
                    Path = path,
                    Name = info.Name,
                    Extension = string.Empty,
                    Size = 0,
                    CreatedUtc = info.CreationTimeUtc,
                    ModifiedUtc = info.LastWriteTimeUtc
                });
            }

            return Task.FromResult(new FileInfoModel
            {
                Exists = false,
                Path = path
            });
        }


        private static void EnsureDistinctPaths(
            string sourcePath,
            string destinationPath)
        {
            if (string.Equals(
                    sourcePath,
                    destinationPath,
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(
                    "Source and destination paths must be different.");
            }
        }
    }
}
