using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using System.Text.RegularExpressions;

namespace Fast.Logic;

public static partial class Util
{
    public static class File
    {
        public static readonly bool isLocalFilesEnabled = false;
        public static async Task<FileUploadResponse<T>> ProcessUploadFileChunked<T>(IFormFile file, string metaData, System.Security.Claims.ClaimsPrincipal user, Func<FileStream, Task<T>> process)
        {
            if (file == null || file.Length == 0)
                return new FileUploadResponse<T> { Success = false, Message = "File is empty or invalid." };

            if (string.IsNullOrWhiteSpace(metaData))
                return new FileUploadResponse<T> { Success = false, Message = "Metadata is required." };

            ChunkMetaData chunkData;
            try
            {
                using var metaDataStream = new MemoryStream(Encoding.UTF8.GetBytes(metaData));
                var serializer = new DataContractJsonSerializer(typeof(ChunkMetaData));
                if (serializer.ReadObject(metaDataStream) is not ChunkMetaData cd)
                    return new FileUploadResponse<T> { Success = false, Message = "Invalid metadata." };
                chunkData = cd;
            }
            catch (Exception)
            {
                return new FileUploadResponse<T> { Success = false, Message = "Invalid metadata format." };
            }

            int userId = GetAppUserId(user);

            // Construct a consistent temporary file path.
            string extension = Path.GetExtension(chunkData.FileName);
            string originalFileName = $"{new Guid(chunkData.FileUid):N}{extension}";
            string tempFileName = $"{userId}_{HashOfOriginalFileName(chunkData.FileUid)}.tmp";
            string tempFilePath = Path.Join(Path.GetTempPath(), tempFileName);

            // Append the current chunk to the temporary file.
            try
            {
                using var fileStream = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
                await file.CopyToAsync(fileStream);
            }
            catch (Exception)
            {
                return new FileUploadResponse<T> { Success = false, Message = "Error saving file chunk." };
            }

            // If this is the last chunk, process the complete file.
            if (chunkData.ChunkIndex == chunkData.TotalChunks - 1)
            {
                try
                {
                    using var combinedFileStream = new FileStream(tempFilePath, FileMode.Open, FileAccess.Read);
                    T processResult = await process(combinedFileStream);
                    return new FileUploadResponse<T> { Success = true, IsComplete = true, Data = processResult, Message = "File processed successfully." };
                }
                catch (Exception ex)
                {
                    var msg = String.GetExceptionMessage(ex);
                    return new FileUploadResponse<T> { Success = false, Message = $"Error processing final file.\r\n{msg}" };
                }
            }

            // Otherwise, the chunk was uploaded successfully but the file is not yet complete.
            return new FileUploadResponse<T> { Success = true, IsComplete = false, Message = "Chunk uploaded successfully." };
        }

        private static string HashOfOriginalFileName(string originalFileName)
        {
            using SHA256 sha256 = SHA256.Create();
            byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(originalFileName));
            return Convert.ToHexString(hashBytes); // Converts byte array to a hex string
        }

        public static string CreateShardedSubdirectory(string sourcePath, string fileName, bool createIfNotExists = true)
        {
            string newfolderName = "";
            // Looks for the last hyphen (with optional spaces), a known ticket prefix,
            // and captures the subsequent number, disregarding any numbers after the ticket number.
            string pattern = @"\s*-\s*(?:GAS|OPT|BSW|FFS|STR|SSW|CTR|BRH|TFR|CRO|FUT)(\d+)";
            Regex ticketNumberRegex = new Regex(pattern, RegexOptions.RightToLeft);
            bool isGuidFile = IsGuidFileName(fileName);
            if (isGuidFile)
            {
                newfolderName = fileName[..2];
            }
            else
            {
                var match = ticketNumberRegex.Match(fileName);
                if (match.Success && int.TryParse(match.Groups[1].Value, out int number))
                {
                    const int rangeSize = 1000;
                    int rangeStart = (number / rangeSize) * rangeSize;
                    newfolderName = $"{rangeStart}-{rangeStart + rangeSize - 1}";
                }
            }

            if (!string.IsNullOrEmpty(newfolderName) && createIfNotExists)
            {
                var fullPath = Path.Join(sourcePath, newfolderName);
                Directory.CreateDirectory(fullPath);
            }
            return newfolderName;
        }

        public static string GetShardedFullPath(string sourcePath, string fileName, bool createIfNotExists = true)
        {
            var subfolder = CreateShardedSubdirectory(sourcePath, fileName, createIfNotExists);
            return Path.Join(sourcePath, subfolder, fileName);
        }

        /// <summary>
        /// Determines if the filename (without its extension) is a valid GUID.
        /// </summary>
        /// <param name="fileName">The full filename or path, e.g., "8ab62d78901c4e96abe2d3cc8663f5a8.pdf"
        /// or "C:\\inetpub\\docs\\8ab62d78901c4e96abe2d3cc8663f5a8.pdf".</param>
        /// <returns>True if the filename (excluding extension) is a valid GUID, false otherwise.</returns>
        public static bool IsGuidFileName(string? fileName)
        {
            if (string.IsNullOrWhiteSpace(fileName))
            {
                return false;
            }

            string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName);
            return Guid.TryParse(fileNameWithoutExtension, out _);
        }
        public static async Task<string?> SaveFileAsync(string contentFolder, string subFolder, IFormFile file)
        {
            if (file.Length == 0)
                return null;

            try
            {
                string extension = Path.GetExtension(file.FileName);
                var randomFileName = Guid.NewGuid().ToString("N") + extension;
                if (isLocalFilesEnabled)
                {
                    var localFullPath = Path.Join(contentFolder, subFolder);
                    string filePath = GetShardedFullPath(localFullPath, randomFileName);
                    await using var fileStream = new FileStream(filePath, FileMode.Create);
                    await file.CopyToAsync(fileStream);
                    return randomFileName;
                }

                var sharePoint = new SharePoint();
                return await sharePoint.UploadFileAsync(randomFileName, subFolder, file);
            }
            catch (Exception)
            {
                return null;
            }
        }

        public static async Task<string?> DeleteFileAsync(string contentFolder, string subFolder, string fileNameOnDisk)
        {
            if (isLocalFilesEnabled)
            {
                var localFullPath = Path.Join(contentFolder, subFolder);
                string filePath = GetShardedFullPath(localFullPath, fileNameOnDisk, false);
                System.IO.File.Delete(filePath);
                return fileNameOnDisk;
            }

            var sharePoint = new SharePoint();
            var isDeleteSuccesful = await sharePoint.DeleteFileAsync(subFolder, fileNameOnDisk);
            if (isDeleteSuccesful)
                return fileNameOnDisk;

            return null;
        }

        public static async Task<FileDownloadResponse> GetFileAsync(string contentFolder, string subFolder, string fileNameOnDisk)
        {
            try
            {
                if (isLocalFilesEnabled)
                {
                    var localFullPath = Path.Join(contentFolder, subFolder);
                    var fullPath = GetShardedFullPath(localFullPath, fileNameOnDisk, false);
                    var localStream = System.IO.File.OpenRead(fullPath);
                    return new FileDownloadResponse { Stream = localStream, ContentType = "application/octet-stream", FileName = fileNameOnDisk };
                }

                var sharePoint = new SharePoint();
                var stream = await sharePoint.DownloadFileAsync(subFolder, fileNameOnDisk);
                if (stream == null)
                {
                    throw new FileNotFoundException($"The file '{fileNameOnDisk}' was not found on SharePoint.", fileNameOnDisk);
                }

                return new FileDownloadResponse { Stream = stream, ContentType = "application/octet-stream", FileName = fileNameOnDisk };
            }
            catch (Exception ex)
            {
                var bytes = GetExceptionFilesBytes(ex);
                var memoryStream = new MemoryStream(bytes);
                return new FileDownloadResponse { Stream = memoryStream, ContentType = "text/plain", FileName = "error.txt" };
            }
        }

    }
}

public class FileDownloadResponse
{
    public Stream Stream { get; set; } = Stream.Null;
    public string ContentType { get; set; } = string.Empty;
    public string FileName { get; set; } = string.Empty;
}

public class FileUploadResponse<T>
{
    public bool Success { get; set; } // Indicates whether the operation was successful.
    public bool IsComplete { get; set; } // Set to true when the file upload has been completed and processed.
    public string Message { get; set; } = ""; // A message describing the result.
    public T? Data { get; set; } // The data returned from processing the complete file.
}
