using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Threading;

using CommunityToolkit.Mvvm.ComponentModel;

namespace SourceGit.ViewModels
{
    public record InteractiveRebasePrefill(string SHA, Models.InteractiveRebaseAction Action);
    public record InteractiveRebaseReorderItem(string Key, InteractiveRebaseItem Item);

    public class InteractiveRebaseItem : ObservableObject
    {
        public int OriginalOrder
        {
            get;
        }

        public Models.Commit Commit
        {
            get;
        }

        public Models.InteractiveRebaseAction Action
        {
            get => _action;
            set => SetProperty(ref _action, value);
        }

        public Models.InteractiveRebasePendingType PendingType
        {
            get => _pendingType;
            set => SetProperty(ref _pendingType, value);
        }

        public string Subject
        {
            get => _subject;
            private set => SetProperty(ref _subject, value);
        }

        public string FullMessage
        {
            get => _fullMessage;
            set
            {
                if (SetProperty(ref _fullMessage, value))
                {
                    var normalized = value.ReplaceLineEndings("\n");
                    var parts = normalized.Split("\n\n", 2);
                    Subject = parts[0].ReplaceLineEndings(" ");
                }
            }
        }

        public string OriginalFullMessage
        {
            get;
            set;
        }

        public bool CanSquashOrFixup
        {
            get => _canSquashOrFixup;
            set => SetProperty(ref _canSquashOrFixup, value);
        }

        public bool ShowEditMessageButton
        {
            get => _showEditMessageButton;
            set => SetProperty(ref _showEditMessageButton, value);
        }

        public Thickness DropDirectionIndicator
        {
            get => _dropDirectionIndicator;
            set => SetProperty(ref _dropDirectionIndicator, value);
        }

        public bool IsMessageUserEdited
        {
            get;
            set;
        } = false;

        public InteractiveRebaseItem(int order, Models.Commit c, string message)
        {
            OriginalOrder = order;
            Commit = c;
            FullMessage = message;
            OriginalFullMessage = message;
        }

        private Models.InteractiveRebaseAction _action = Models.InteractiveRebaseAction.Pick;
        private Models.InteractiveRebasePendingType _pendingType = Models.InteractiveRebasePendingType.None;
        private string _subject;
        private string _fullMessage;
        private bool _canSquashOrFixup = true;
        private bool _showEditMessageButton = false;
        private Thickness _dropDirectionIndicator = new Thickness(0);
    }

    public class InteractiveRebase : ObservableObject
    {
        public Models.Branch Current
        {
            get;
            private set;
        }

        public Models.Commit On
        {
            get;
        }

        public bool AutoStash
        {
            get;
            set;
        } = true;

        public bool NoVerify
        {
            get;
            set;
        }

        public AvaloniaList<Models.IssueTracker> IssueTrackers
        {
            get => _repo.IssueTrackers;
        }

        public string ConventionalTypesOverride
        {
            get => _repo.Settings.ConventionalTypesOverride;
        }

        public bool IsLoading
        {
            get => _isLoading;
            private set => SetProperty(ref _isLoading, value);
        }

        public AvaloniaList<InteractiveRebaseItem> Items
        {
            get;
        } = [];

        public InteractiveRebaseItem PreSelected
        {
            get => _preSelected;
            private set => SetProperty(ref _preSelected, value);
        }

        public object Detail
        {
            get => _detail;
            private set => SetProperty(ref _detail, value);
        }

        public InteractiveRebase(Repository repo, Models.Commit on, InteractiveRebasePrefill prefill = null)
        {
            _repo = repo;
            _commitDetail = new CommitDetail(repo, null);
            Current = repo.CurrentBranch;
            On = on;
            IsLoading = true;

            Task.Run(async () =>
            {
                var commits = await new Commands.QueryCommitsForInteractiveRebase(_repo.FullPath, on.SHA)
                    .GetResultAsync()
                    .ConfigureAwait(false);

                var list = new List<InteractiveRebaseItem>();
                var needReorder = new List<InteractiveRebaseReorderItem>();
                var needReorderKeySet = new HashSet<string>();

                // First-pass to find out the commits that need to be reordered and leave the reset in original order.
                for (var i = 0; i < commits.Count; i++)
                {
                    var c = commits[i];
                    var item = new InteractiveRebaseItem(commits.Count - i, c.Commit, c.Message);
                    var subject = c.Commit.Subject;

                    if (item.OriginalOrder > 1)
                    {
                        if (subject.StartsWith("fixup! ", StringComparison.Ordinal))
                        {
                            item.Action = Models.InteractiveRebaseAction.Fixup;

                            var targetSubject = subject.Substring(7).Trim();
                            needReorder.Add(new(targetSubject, item));
                            needReorderKeySet.Add(targetSubject);
                            continue;
                        }

                        if (subject.StartsWith("squash! ", StringComparison.Ordinal))
                        {
                            item.Action = Models.InteractiveRebaseAction.Squash;

                            var targetSubject = subject.Substring(8).Trim();
                            needReorder.Add(new(targetSubject, item));
                            needReorderKeySet.Add(targetSubject);
                            continue;
                        }
                    }

                    list.Add(item);
                }

                // Second-pass to find the place for reordered commits and insert them.
                for (var i = list.Count - 1; i >= 0; i--)
                {
                    var subject = list[i].Commit.Subject.Trim();
                    if (!needReorderKeySet.Contains(subject))
                        continue;

                    for (var j = needReorder.Count - 1; j >= 0; j--)
                    {
                        var test = needReorder[j];
                        if (subject.Equals(test.Key, StringComparison.Ordinal))
                        {
                            list.Insert(i, test.Item);
                            needReorder.RemoveAt(j);
                        }
                    }

                    needReorderKeySet.Remove(subject);
                    if (needReorderKeySet.Count == 0)
                        break;
                }

                // Last-pass to insert the remaining commits that are marked as fixup!/squash! but their target commit is not found.
                foreach (var v in needReorder)
                {
                    // For safety, reset to pick if the target commit is not found
                    v.Item.Action = Models.InteractiveRebaseAction.Pick;

                    // Find out the first picked (exclude reordered) commit that should be picked after this commit
                    var insertPos = 0;
                    for (var i = 0; i < list.Count; i++)
                    {
                        var item = list[i];
                        if (item.Action == Models.InteractiveRebaseAction.Pick)
                        {
                            if (v.Item.OriginalOrder < item.OriginalOrder)
                                insertPos = i + 1;
                            else
                                break;
                        }
                    }

                    list.Insert(insertPos, v.Item);
                }

                var selected = list.Count > 0 ? list[0] : null;
                if (prefill != null)
                {
                    var item = list.Find(x => x.Commit.SHA.Equals(prefill.SHA, StringComparison.Ordinal));
                    if (item != null)
                    {
                        item.Action = prefill.Action;
                        selected = item;
                    }
                }

                Dispatcher.UIThread.Post(() =>
                {
                    Items.AddRange(list);
                    UpdateItems();
                    PreSelected = selected;
                    IsLoading = false;
                });
            });
        }

        public void SelectCommits(List<InteractiveRebaseItem> items)
        {
            if (items.Count == 0)
            {
                Detail = null;
            }
            else if (items.Count == 1)
            {
                _commitDetail.Commit = items[0].Commit;
                Detail = _commitDetail;
            }
            else
            {
                Detail = new Models.Count(items.Count);
            }
        }

        public void ChangeAction(List<InteractiveRebaseItem> selected, Models.InteractiveRebaseAction action)
        {
            if (action == Models.InteractiveRebaseAction.Squash || action == Models.InteractiveRebaseAction.Fixup)
            {
                foreach (var item in selected)
                {
                    if (item.CanSquashOrFixup)
                        item.Action = action;
                }
            }
            else
            {
                foreach (var item in selected)
                    item.Action = action;
            }

            UpdateItems();
        }

        public void Move(List<InteractiveRebaseItem> commits, int index)
        {
            var hashes = new HashSet<string>();
            foreach (var c in commits)
                hashes.Add(c.Commit.SHA);

            var before = new List<InteractiveRebaseItem>();
            var ordered = new List<InteractiveRebaseItem>();
            var after = new List<InteractiveRebaseItem>();

            for (int i = 0; i < index; i++)
            {
                var item = Items[i];
                if (!hashes.Contains(item.Commit.SHA))
                    before.Add(item);
                else
                    ordered.Add(item);
            }

            for (int i = index; i < Items.Count; i++)
            {
                var item = Items[i];
                if (!hashes.Contains(item.Commit.SHA))
                    after.Add(item);
                else
                    ordered.Add(item);
            }

            Items.Clear();
            Items.AddRange(before);
            Items.AddRange(ordered);
            Items.AddRange(after);
            UpdateItems();
        }

        public async Task<bool> Start()
        {
            using var lockWatcher = _repo.LockWatcher();

            var saveFile = Path.Combine(_repo.GitDir, "sourcegit.interactive_rebase");
            var collection = new Models.InteractiveRebaseJobCollection();
            collection.OrigHead = _repo.CurrentBranch.Head;
            collection.Onto = On.SHA;

            InteractiveRebaseItem pending = null;
            for (int i = Items.Count - 1; i >= 0; i--)
            {
                var item = Items[i];
                var job = new Models.InteractiveRebaseJob()
                {
                    SHA = item.Commit.SHA,
                    Action = item.Action,
                };

                if (pending != null && item.PendingType != Models.InteractiveRebasePendingType.Ignore)
                    job.Message = pending.FullMessage;
                else
                    job.Message = item.FullMessage;

                collection.Jobs.Add(job);

                if (item.PendingType == Models.InteractiveRebasePendingType.Last)
                    pending = null;
                else if (item.PendingType == Models.InteractiveRebasePendingType.Target)
                    pending = item;
            }

            await using (var stream = File.Create(saveFile))
            {
                await JsonSerializer.SerializeAsync(stream, collection, JsonCodeGen.Default.InteractiveRebaseJobCollection);
            }

            var log = _repo.CreateLog("Interactive Rebase");
            var succ = await new Commands.InteractiveRebase(_repo.FullPath, On.SHA, AutoStash, NoVerify)
                .Use(log)
                .ExecAsync();

            log.Complete();
            return succ;
        }

        private void UpdateItems()
        {
            if (Items.Count == 0)
                return;

            var hasValidParent = false;
            for (var i = Items.Count - 1; i >= 0; i--)
            {
                var item = Items[i];
                if (hasValidParent)
                {
                    item.CanSquashOrFixup = true;
                }
                else
                {
                    item.CanSquashOrFixup = false;
                    if (item.Action == Models.InteractiveRebaseAction.Squash || item.Action == Models.InteractiveRebaseAction.Fixup)
                        item.Action = Models.InteractiveRebaseAction.Pick;

                    hasValidParent = item.Action != Models.InteractiveRebaseAction.Drop;
                }
            }

            var hasPending = false;
            var pendingMessages = new List<string>();
            for (var i = 0; i < Items.Count; i++)
            {
                var item = Items[i];

                if (item.Action == Models.InteractiveRebaseAction.Drop)
                {
                    item.ShowEditMessageButton = false;
                    item.PendingType = hasPending ? Models.InteractiveRebasePendingType.Ignore : Models.InteractiveRebasePendingType.None;
                    item.FullMessage = item.OriginalFullMessage;
                    item.IsMessageUserEdited = false;
                    continue;
                }

                if (item.Action == Models.InteractiveRebaseAction.Fixup ||
                    item.Action == Models.InteractiveRebaseAction.Squash)
                {
                    item.ShowEditMessageButton = false;
                    item.PendingType = hasPending ? Models.InteractiveRebasePendingType.Pending : Models.InteractiveRebasePendingType.Last;
                    item.FullMessage = item.OriginalFullMessage;
                    item.IsMessageUserEdited = false;

                    if (item.Action == Models.InteractiveRebaseAction.Squash)
                    {
                        if (item.OriginalFullMessage.StartsWith("squash! ", StringComparison.Ordinal))
                        {
                            var firstLineEnd = item.OriginalFullMessage.IndexOf('\n');
                            if (firstLineEnd > 0)
                                pendingMessages.Add(item.OriginalFullMessage.Substring(firstLineEnd + 1));
                        }
                        else
                        {
                            pendingMessages.Add(item.OriginalFullMessage);
                        }
                    }

                    hasPending = true;
                    continue;
                }

                if (item.Action == Models.InteractiveRebaseAction.Reword ||
                    item.Action == Models.InteractiveRebaseAction.Edit)
                {
                    var oldPendingType = item.PendingType;
                    item.ShowEditMessageButton = true;
                    item.PendingType = hasPending ? Models.InteractiveRebasePendingType.Target : Models.InteractiveRebasePendingType.None;

                    if (hasPending)
                    {
                        if (!item.IsMessageUserEdited)
                        {
                            var builder = new StringBuilder();
                            builder.Append(item.OriginalFullMessage);
                            for (var j = pendingMessages.Count - 1; j >= 0; j--)
                                builder.Append("\n").Append(pendingMessages[j]);

                            item.FullMessage = builder.ToString();
                        }

                        hasPending = false;
                        pendingMessages.Clear();
                    }
                    else if (oldPendingType == Models.InteractiveRebasePendingType.Target)
                    {
                        if (!item.IsMessageUserEdited)
                            item.FullMessage = item.OriginalFullMessage;
                    }

                    continue;
                }

                if (item.Action == Models.InteractiveRebaseAction.Pick)
                {
                    item.IsMessageUserEdited = false;

                    if (hasPending)
                    {
                        if (pendingMessages.Count > 0)
                        {
                            var builder = new StringBuilder();
                            builder.Append(item.OriginalFullMessage);
                            for (var j = pendingMessages.Count - 1; j >= 0; j--)
                                builder.Append("\n").Append(pendingMessages[j]);

                            item.Action = Models.InteractiveRebaseAction.Reword;
                            item.PendingType = Models.InteractiveRebasePendingType.Target;
                            item.ShowEditMessageButton = true;
                            item.FullMessage = builder.ToString();
                        }
                        else
                        {
                            item.PendingType = Models.InteractiveRebasePendingType.Target;
                            item.ShowEditMessageButton = false;
                            item.FullMessage = item.OriginalFullMessage;
                        }

                        hasPending = false;
                        pendingMessages.Clear();
                    }
                    else
                    {
                        item.PendingType = Models.InteractiveRebasePendingType.None;
                        item.ShowEditMessageButton = false;
                        item.FullMessage = item.OriginalFullMessage;
                    }
                }
            }
        }

        private Repository _repo = null;
        private bool _isLoading = false;
        private InteractiveRebaseItem _preSelected = null;
        private object _detail = null;
        private CommitDetail _commitDetail = null;
    }
}
