using CliWrap;
using CliWrap.Buffered;
using InnovEnergy.Lib.Utils;

namespace InnovEnergy.FossilTui;

public static class Program
{
    private static readonly Command Fossil = Cli.Wrap("fossil");
    
    public static async Task Main()
    {
        Console.CancelKeyPress += (_, _) => "aborted".WriteLine();
        
        while (true)
        {
            var needsUpdate = await NeedsUpdate();
            var changes     = await GetChanges();
            var status      = await GetStatus();

            if (!changes.Any())
            {
                Console.Clear();
                status.WriteLine(Environment.NewLine);
                "No Changes".WriteLine(ConsoleColor.Green);
                return;
            }
            
            var selectedChange = changes.First();
            
            while (true)
            {
                Console.Clear();
                status.WriteLine(Environment.NewLine);
                
                if (needsUpdate)
                {
                    "Update available for your branch".WriteLine(ConsoleColor.Red); 
                    "Press U to Update".WriteLine(Environment.NewLine);
                }
                
                ShowChanges(changes, selectedChange);
              
                var key = Console.ReadKey().Key;

                if (key == ConsoleKey.DownArrow)
                {
                    selectedChange = changes.GetNext(selectedChange);
                }
                else if (key == ConsoleKey.UpArrow)
                {
                    selectedChange = changes.GetPrevious(selectedChange);
                }
                else if (key == ConsoleKey.RightArrow)
                {
                    selectedChange.SelectNextAction(!needsUpdate);
                }
                else if (key == ConsoleKey.LeftArrow)
                {
                    selectedChange.SelectPreviousAction(!needsUpdate);
                }
                else if (key == ConsoleKey.D && selectedChange.State == FossilState.Edited)
                {
                    var fossilGetDiff = Fossil.WithArguments("gdiff " + selectedChange.File);
                    await fossilGetDiff.ExecuteBufferedAsync();
                }
                else if (key == ConsoleKey.U)
                {
                    await FossilUpdate();
                    break;
                }
                else if (key == ConsoleKey.Spacebar)
                {
                    //
                }
                else if (key == ConsoleKey.Escape)
                {
                    Console.Clear();
                    return;
                }
                else if (key == ConsoleKey.Enter)
                {
                    await ApplyActions(changes, needsUpdate);
                    break;
                }
                
                Console.Clear();
            }
        }
    }

    private static async Task<IReadOnlyList<Change>> GetChanges()
    {
        var result = await Fossil
                          .WithArguments("changes --differ")
                          .ExecuteBufferedAsync();

        return result
              .StandardOutput
              .SplitLines()
              .Unless(String.IsNullOrWhiteSpace)
              //.Unless(f => f.Contains("FossilTui"))    // TODO: remove
              .Select(l => l.Split(' ', StringSplitOptions.RemoveEmptyEntries))
              .Select(t => new Change(FossilState.Parse(t[0]), t[1]))
              .ToList();
    }

    private static async Task<String> GetStatus()
    {
        var statusResult = await Fossil
                                .WithArguments("status")
                                .ExecuteBufferedAsync();

        return statusResult
              .StandardOutput
              .SplitLines()
              .Unless(String.IsNullOrWhiteSpace)
              .Where(l => l[0] is >= 'a' and <= 'z')
              .JoinNonEmptyLines();
    }

    private static async Task FossilUpdate()
    {
        var result = await Fossil
                          .WithArguments("update")
                          .WithValidation(CommandResultValidation.None)
                          .ExecuteBufferedAsync();
        
        if (result.ExitCode != 0)
        {
            Console.WriteLine(result.StandardOutput);
            Console.WriteLine(result.StandardError);
        }
    }

    private static async Task ApplyActions(IReadOnlyList<Change> changes, Boolean needsUpdate)
    {
        await DeleteFiles(changes);
        await AddFiles(changes);  // must run before CommitFiles
        await RevertFiles(changes);
        await CommitFiles(changes, needsUpdate);
    }

    private static async Task CommitFiles(IReadOnlyList<Change> changes, Boolean needsUpdate)
    {
        var filesToCheckIn = changes.GetFilesWithAction(FossilAction.Commit);

        if (!filesToCheckIn.Any())
            return;

        if (needsUpdate)
        {
            "Would fork".WriteLine(ConsoleColor.Red);
            "Not committing selected files".WriteLine();
            "Please update first".WriteLine();
        }
        else
        {
            var msg   = RequestCommitMessage().Quote();
            var files = filesToCheckIn.JoinWith(" ");

            var result = await Fossil
                              .WithArguments($"ci -m {msg} {files}")
                              .ExecuteBufferedAsync();
            
            if (result.ExitCode != 0)
            {
                Console.WriteLine(result.StandardOutput);
                Console.WriteLine(result.StandardError);
            }
        }
    }

    private static String RequestCommitMessage()
    {
        while(true)
        {
            Console.WriteLine("Write your Commit message:");
            var msg = Console.ReadLine();
            
            if (msg.IsNullOrWhiteSpace()) 
                continue;
            
            return msg!;
        } 
    }

    private static async Task RevertFiles(IReadOnlyList<Change> changes)
    {
        var filesToRevert = changes.GetFilesWithAction(FossilAction.Revert);
        if (!filesToRevert.Any())
            return;

        var args   = filesToRevert.Aggregate("revert", (a, b) => a + " " + b);
        var result = await Fossil.WithArguments(args).ExecuteBufferedAsync();
        if (result.ExitCode != 0)
        {
            Console.WriteLine(result.StandardOutput);
            Console.WriteLine(result.StandardError);
        }
    }

    private static async Task AddFiles(IReadOnlyList<Change> changes)
    {
        var args = changes
                  .Where(c => c.SelectedAction == FossilAction.Add)
                  .Select(c => c.File)
                  .Aggregate("add", (a, b) => a + " " + b);
      
        var result = await Fossil.WithArguments(args).ExecuteBufferedAsync();
        if (result.ExitCode != 0)
        {
            Console.WriteLine(result.StandardOutput);
            Console.WriteLine(result.StandardError);
        }
    }

    private static async Task DeleteFiles(IReadOnlyCollection<Change> changes)
    {
        foreach (var file in changes.GetFilesWithAction(FossilAction.Delete))
        {
            var result = await Fossil.WithArguments($"rm {file}").WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();
            if (result.ExitCode != 0)
            {
                Console.WriteLine(result.StandardOutput);
                Console.WriteLine(result.StandardError);
            }

            try
            {
                File.Delete(file);
            }
            catch (Exception e)
            {
                Console.WriteLine($"Failed to delete file {file}"); 
                Console.WriteLine(e);
            }
        }
        
    }

    private static IReadOnlyList<String> GetFilesWithAction(this IEnumerable<Change> changes, FossilAction fossilAction)
    {
        return changes
              .Where(c => c.SelectedAction == fossilAction)
              .Select(c => c.File)
              .ToList();

    }

    private static void ShowChanges(IReadOnlyList<Change> changes, Change selectedChange)
    {
        foreach (var change in changes)
        {
            const ConsoleColor bg = ConsoleColor.Black;
            const ConsoleColor fg = ConsoleColor.Gray;
            var state = change.State;
            
            var stateFgColor = state.Color;
            
            var selected = change.File == selectedChange.File;
            
            state.Name.PadRight(10).Write(stateFgColor, bg, selected);
            change.SelectedAction.ToString().PadRight(10).Write(fg, bg, selected);
            change.File.Write(fg, bg, selected);
            
            Console.WriteLine();
        }
    }

    private static async Task<Boolean> NeedsUpdate()
    {
        var result = await Fossil
                          .WithArguments("update -n")
                          .ExecuteBufferedAsync();

        return result
              .StandardOutput
              .SplitLines()
              .Any(l => l.StartsWith("updated-to"));
    }

}