using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;

namespace SourceGit.ViewModels
{
    public class FileHistoriesRevisionFile(string path, object content = null, bool canOpenWithDefaultEditor = false)
    {
        public string Path { get; set; } = path;
        public object Content { get; set; } = content;
        public bool CanOpenWithDefaultEditor { get; set; } = canOpenWithDefaultEditor;
    }

    public class FileHistoriesSingleRevisionViewMode
    {
        public bool IsDiff
        {
            get;
            set;
        } = true;
    }

    public class FileHistoriesSingleRevision : ObservableObject
    {
        public bool IsDiffMode
        {
            get => _viewMode.IsDiff;
            set
            {
                if (_viewMode.IsDiff != value)
                {
                    _viewMode.IsDiff = value;
                    RefreshViewContent();
                }
            }
        }

        public object ViewContent
        {
            get => _viewContent;
            set => SetProperty(ref _viewContent, value);
        }

        public FileHistoriesSingleRevision(string repo, Models.FileVersion revision, FileHistoriesSingleRevisionViewMode viewMode)
        {
            _repo = repo;
            _file = revision.Path;
            _revision = revision;
            _viewMode = viewMode;
            _viewContent = null;

            RefreshViewContent();
        }

        public void SetRevision(Models.FileVersion revision)
        {
            _file = revision.Path;
            _revision = revision;
            RefreshViewContent();
        }

        public async Task<bool> ResetToSelectedRevisionAsync()
        {
            return await new Commands.Checkout(_repo)
                .FileWithRevisionAsync(_file, $"{_revision.SHA}")
                .ConfigureAwait(false);
        }

        public async Task OpenWithDefaultEditorAsync()
        {
            if (_viewContent is not FileHistoriesRevisionFile { CanOpenWithDefaultEditor: true })
                return;

            var fullPath = Native.OS.GetAbsPath(_repo, _file);
            var fileName = Path.GetFileNameWithoutExtension(fullPath) ?? "";
            var fileExt = Path.GetExtension(fullPath) ?? "";
            var tmpFile = Path.Combine(Path.GetTempPath(), $"{fileName}~{_revision.SHA.AsSpan(0, 10)}{fileExt}");

            await Commands.SaveRevisionFile
                .RunAsync(_repo, _revision.SHA, _file, tmpFile)
                .ConfigureAwait(false);

            Native.OS.OpenWithDefaultEditor(tmpFile);
        }

        private void RefreshViewContent()
        {
            if (_viewMode.IsDiff)
            {
                ViewContent = new DiffContext(_repo, new(_revision), _viewContent as DiffContext);
                return;
            }

            Task.Run(async () =>
            {
                var objs = await new Commands.QueryRevisionObjects(_repo, _revision.SHA, _file)
                    .GetResultAsync()
                    .ConfigureAwait(false);

                if (objs.Count == 0)
                {
                    Dispatcher.UIThread.Post(() => ViewContent = new FileHistoriesRevisionFile(_file));
                    return;
                }

                var revisionContent = await GetRevisionFileContentAsync(objs[0]).ConfigureAwait(false);
                Dispatcher.UIThread.Post(() => ViewContent = revisionContent);
            });
        }

        private async Task<object> GetRevisionFileContentAsync(Models.Object obj)
        {
            if (obj.Type == Models.ObjectType.Blob)
            {
                var isBinary = await new Commands.IsBinary(_repo, _revision.SHA, _file).GetResultAsync().ConfigureAwait(false);
                if (isBinary)
                {
                    var imgDecoder = ImageSource.GetDecoder(_file);
                    if (imgDecoder != Models.ImageDecoder.None)
                    {
                        var source = await ImageSource.FromRevisionAsync(_repo, _revision.SHA, _file, imgDecoder).ConfigureAwait(false);
                        var image = new Models.RevisionImageFile(_file, source.Bitmap, source.Size);
                        return new FileHistoriesRevisionFile(_file, image, true);
                    }

                    var size = await new Commands.QueryFileSize(_repo, _file, _revision.SHA).GetResultAsync().ConfigureAwait(false);
                    var binaryFile = new Models.RevisionBinaryFile() { Size = size };
                    return new FileHistoriesRevisionFile(_file, binaryFile, true);
                }

                var contentStream = await Commands.QueryFileContent.RunAsync(_repo, _revision.SHA, _file).ConfigureAwait(false);
                var content = await new StreamReader(contentStream).ReadToEndAsync();
                var lfs = Models.LFSObject.Parse(content);
                if (lfs != null)
                {
                    var imgDecoder = ImageSource.GetDecoder(_file);
                    if (imgDecoder != Models.ImageDecoder.None)
                    {
                        var combined = new RevisionLFSImage(_repo, _file, lfs, imgDecoder);
                        return new FileHistoriesRevisionFile(_file, combined, true);
                    }

                    var rlfs = new Models.RevisionLFSObject() { Object = lfs };
                    return new FileHistoriesRevisionFile(_file, rlfs, true);
                }

                var txt = new Models.RevisionTextFile() { FileName = obj.Path, Content = content };
                return new FileHistoriesRevisionFile(_file, txt, true);
            }

            if (obj.Type == Models.ObjectType.Commit)
            {
                var submoduleRoot = Path.Combine(_repo, _file);
                var commit = await new Commands.QuerySingleCommit(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false);
                var message = commit != null ? await new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).GetResultAsync().ConfigureAwait(false) : null;
                var module = new Models.RevisionSubmodule()
                {
                    Commit = commit ?? new Models.Commit() { SHA = obj.SHA },
                    FullMessage = new Models.CommitFullMessage { Message = message }
                };

                return new FileHistoriesRevisionFile(_file, module);
            }

            return new FileHistoriesRevisionFile(_file);
        }

        private string _repo = null;
        private string _file = null;
        private Models.FileVersion _revision = null;
        private FileHistoriesSingleRevisionViewMode _viewMode = null;
        private object _viewContent = null;
    }

    public class FileHistoriesCompareRevisions : ObservableObject
    {
        public Models.FileVersion StartPoint
        {
            get => _startPoint;
            set => SetProperty(ref _startPoint, value);
        }

        public Models.FileVersion EndPoint
        {
            get => _endPoint;
            set => SetProperty(ref _endPoint, value);
        }

        public DiffContext ViewContent
        {
            get => _viewContent;
            set => SetProperty(ref _viewContent, value);
        }

        public FileHistoriesCompareRevisions(string repo, Models.FileVersion start, Models.FileVersion end)
        {
            _repo = repo;
            _startPoint = start;
            _endPoint = end;
            _viewContent = new(_repo, new(start, end));
        }

        public void Swap()
        {
            (StartPoint, EndPoint) = (_endPoint, _startPoint);
            ViewContent = new(_repo, new(_startPoint, _endPoint), _viewContent);
        }

        public async Task<bool> SaveAsPatch(string saveTo)
        {
            return await Commands.SaveChangesAsPatch
                .ProcessRevisionCompareChangesAsync(_repo, _changes, _startPoint.SHA, _endPoint.SHA, saveTo)
                .ConfigureAwait(false);
        }

        private string _repo = null;
        private Models.FileVersion _startPoint = null;
        private Models.FileVersion _endPoint = null;
        private List<Models.Change> _changes = [];
        private DiffContext _viewContent = null;
    }

    public class FileHistories : ObservableObject
    {
        public string Title
        {
            get;
        }

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

        public List<Models.FileVersion> Revisions
        {
            get => _revisions;
            set => SetProperty(ref _revisions, value);
        }

        public List<Models.FileVersion> SelectedRevisions
        {
            get => _selectedRevisions;
            set
            {
                if (SetProperty(ref _selectedRevisions, value))
                    RefreshViewContent();
            }
        }

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

        public FileHistories(string repo, string file, string commit = null)
        {
            if (!string.IsNullOrEmpty(commit))
                Title = $"{file} @ {commit}";
            else
                Title = file;

            _repo = repo;

            Task.Run(async () =>
            {
                var revisions = await new Commands.QueryFileHistory(_repo, file, commit)
                    .GetResultAsync()
                    .ConfigureAwait(false);

                Dispatcher.UIThread.Post(() =>
                {
                    IsLoading = false;
                    Revisions = revisions;
                });
            });
        }

        public void NavigateToCommit(Models.FileVersion revision)
        {
            var launcher = App.GetLauncher();
            if (launcher != null)
            {
                foreach (var page in launcher.Pages)
                {
                    if (page.Data is Repository repo && repo.FullPath.Equals(_repo, StringComparison.Ordinal))
                    {
                        repo.NavigateToCommit(revision.SHA);
                        break;
                    }
                }
            }
        }

        public string GetCommitFullMessage(Models.FileVersion revision)
        {
            var sha = revision.SHA;
            if (_fullCommitMessages.TryGetValue(sha, out var msg))
                return msg;

            msg = new Commands.QueryCommitFullMessage(_repo, sha).GetResult();
            _fullCommitMessages[sha] = msg;
            return msg;
        }

        private void RefreshViewContent()
        {
            var count = _selectedRevisions?.Count ?? 0;
            if (count == 0)
            {
                ViewContent = null;
            }
            else if (count == 1)
            {
                if (_viewContent is FileHistoriesSingleRevision single)
                    single.SetRevision(_selectedRevisions[0]);
                else
                    ViewContent = new FileHistoriesSingleRevision(_repo, _selectedRevisions[0], _viewMode);
            }
            else if (count == 2)
            {
                ViewContent = new FileHistoriesCompareRevisions(_repo, _selectedRevisions[0], _selectedRevisions[1]);
            }
            else
            {
                ViewContent = _selectedRevisions.Count;
            }
        }

        private readonly string _repo = null;
        private bool _isLoading = true;
        private FileHistoriesSingleRevisionViewMode _viewMode = new();
        private List<Models.FileVersion> _revisions = null;
        private List<Models.FileVersion> _selectedRevisions = [];
        private Dictionary<string, string> _fullCommitMessages = new();
        private object _viewContent = null;
    }
}
