using Abyss.Components.Services.Misc;
using Abyss.Components.Services.Security;
using Abyss.Components.Static;
using Abyss.Model.Media;
using Newtonsoft.Json;
using SQLite;
using Task = Abyss.Model.Media.Task;

namespace Abyss.Components.Services.Media;



public class TaskService(ConfigureService config, ResourceService rs, UserService user)
{ 
    public readonly string TaskFolder = Path.Combine(config.MediaRoot, "Tasks");
    public readonly string VideoFolder = Path.Combine(config.MediaRoot, "Videos");
    
    private const ulong MaxChunkSize = 20 * 1024 * 1024;
    
    public async Task<List<String>> Query(string token, string ip)
    {
        var r = await rs.Query(TaskFolder, token, ip);
        var u = user.Validate(token, ip);
        
        List<string> s = new();
        foreach (var i in r ?? [])
        {
            var p = Helpers.SafePathCombine(TaskFolder, [i, "task.json"]);
            var c = JsonConvert.DeserializeObject<Task>(await File.ReadAllTextAsync(p ?? ""));
            
            if(c?.Owner == u) s.Add(i);
        }
        
        return s;
    }

    public async Task<TaskCreationResponse?> Create(string token, string ip, TaskCreation creation)
    {
        if(creation.Name.Length > 64 || creation.Klass.Length > 16 || creation.Size > 10UL * 1024UL * 1024UL * 1024UL || creation.Author.Length > 32)
            return null;
        if(creation.Name == "" || creation.Klass == "")
            return null;
        if(!IsFileNameSafe(creation.Klass))
            return null;
        if (GetAvailableFreeSpace(TaskFolder) - (long)creation.Size < 10 * 1024L * 1024L * 1024L)
        { // Reserve 10GB of space
            return null;
        }
        
        switch ((TaskType)creation.Type)
        {
            case TaskType.Image:
                throw new NotImplementedException();
                // return await CreateImageTask(token, ip, creation);
            case TaskType.Video:
                return await CreateVideoTask(token, ip, creation);
            default:
                return null;
        }
    }

    private async Task<TaskCreationResponse?> CreateVideoTask(string token, string ip, TaskCreation creation)
    {
        var u = user.Validate(token, ip);
        if(u == -1)
            return null;
        
        var r = new TaskCreationResponse()
        {
            Id = GenerateUniqueId(TaskFolder),
            Chips = SliceFile(creation.Size)
        };
        
        Directory.CreateDirectory(Path.Combine(TaskFolder, r.Id.ToString())); 
        Directory.CreateDirectory(Path.Combine(TaskFolder, r.Id.ToString(), "gallery")); 
        // It shouldn't be a problem to spell it directly like this, as all the parameters are generated by myself

        Task v = new Task
        {
            Name = creation.Name,
            Owner = u,
            Class = creation.Klass,
            Id = r.Id,
            Type = TaskType.Video
        };
        
        await File.WriteAllTextAsync(
            Path.Combine(TaskFolder, r.Id.ToString(), "task.json"), 
            JsonConvert.SerializeObject(v, Formatting.Indented));

        using (var connection = new SQLiteConnection(Path.Combine(TaskFolder, r.Id.ToString(), "task.db")))
        {
            connection.CreateTable<Chip>();
            connection.InsertAll(r.Chips.Select(x => new Chip()
            {
                Addr = x.Addr,
                Hash = "",
                Id = x.Id,
                Size = x.Size,
                State = ChipState.Created
            }));
            connection.Close();
        }

        CreateEmptyFile(Path.Combine(TaskFolder, r.Id.ToString(), "video.mp4"), (long)creation.Size);
        return r;
    }

    // private async Task<TaskCreationResponse?> CreateImageTask(string token, string ip, TaskCreation creation)
    // {
    //     throw new NotImplementedException();
    // }
    
    public static uint GenerateUniqueId(string parentDirectory)
    {
        string[] directories = Directory.GetDirectories(parentDirectory);
        HashSet<uint> existingIds = new HashSet<uint>();

        foreach (string dirPath in directories)
        {
            string dirName = new DirectoryInfo(dirPath).Name;
            if (uint.TryParse(dirName, out uint id))
            {
                if (id != 0)
                {
                    existingIds.Add(id);
                }
            }
        }

        uint newId = 1;
        while (existingIds.Contains(newId))
        {
            newId++;
            if (newId == uint.MaxValue)
            {
                return 0;
            }
        }

        return newId;
    }
    
    public static List<ChipDesc> SliceFile(ulong fileSize)
    {
        var tasks = new List<ChipDesc>();
        if (fileSize == 0)
        {
            return tasks;
        }

        ulong remainingSize = fileSize;
        ulong currentAddr = 0;
        uint id = 0;

        while (remainingSize > 0)
        {
            ulong chunkSize = remainingSize > MaxChunkSize ? MaxChunkSize : remainingSize;

            tasks.Add(new ChipDesc
            {
                Id = id,
                Addr = currentAddr,
                Size = chunkSize
            });

            currentAddr += chunkSize;
            remainingSize -= chunkSize;
            id++;
        }

        return tasks;
    }
    
    public static bool IsFileNameSafe(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return false;
        }

        if (fileName.Contains(Path.DirectorySeparatorChar) ||
            fileName.Contains(Path.AltDirectorySeparatorChar))
        {
            return false;
        }

        char[] invalidChars = Path.GetInvalidFileNameChars();
        if (fileName.Any(c => invalidChars.Contains(c)))
        {
            return false;
        }

        string[] reservedNames = {
            "CON", "PRN", "AUX", "NUL",
            "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
            "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
        };
        string nameWithoutExtension = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant();
        if (reservedNames.Contains(nameWithoutExtension))
        {
            return false;
        }

        return true;
    }
    
    public static void CreateEmptyFile(string filePath, long sizeInBytes)
    {
        using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
        {
            fs.SetLength(sizeInBytes);
        }
    }
    
    public static long GetAvailableFreeSpace(string directoryPath)
    {
        try
        {
            if (string.IsNullOrEmpty(directoryPath))
            {
                return -1;
            }

            string rootPath = Path.GetPathRoot(directoryPath) ?? "";
            
            if (string.IsNullOrEmpty(rootPath))
            {
                return -1;
            }

            DriveInfo driveInfo = new DriveInfo(rootPath);

            if (driveInfo.IsReady)
            {
                return driveInfo.AvailableFreeSpace;
            }
            else
            {
                return -1;
            }
        }
        catch (Exception)
        {
            return -1;
        }
    }
}