using MainShell.Common;
using MainShell.Models;
using MainShell.Recipe.Models;
using Newtonsoft.Json.Linq;
using Stylet;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;

namespace MainShell.Recipe.ViewModel
{
    public abstract class RecipeViewModelBase<TRecipe> : BaseScreen, IRecipeViewModel
        where TRecipe : RecipeBase, new()
    {
        private string _lastSavedSnapshot = string.Empty;
        protected string LastSavedSnapshot => _lastSavedSnapshot;
        private bool _skipLeaveCheck;
        private RecipeWrap _selectedRecipeWrap;

        public ObservableCollection<RecipeWrap> RecipeWraps { get; protected set; } = new ObservableCollection<RecipeWrap>();

        public RecipeWrap SelectedRecipeWrap
        {
            get { return _selectedRecipeWrap; }
            set
            {
                if (_selectedRecipeWrap == value)
                    return;

                if (!_skipLeaveCheck && _selectedRecipeWrap != null && value != null && !TryLeaveCurrentRecipeContext())
                {
                    NotifyOfPropertyChange(() => SelectedRecipeWrap);
                    return;
                }

                if (SetAndNotify(ref _selectedRecipeWrap, value))
                {
                    LoadRecipeFromWrap(value);
                }
            }
        }

        public RecipeButtonGroupViewModel RecipeButtonGroupViewModel { get; protected set; }
        public virtual bool CanCopyRecipe => true;
        public virtual bool CanImportRecipe => false;
        public virtual bool CanExportRecipe => false;
        public bool HasUnsavedChanges => IsRecipeDirty();
        public string CurrentRecipeDisplayName => CurrentRecipe != null ? CurrentRecipe.RecipeName : EmptyRecipeDisplayName;
        public RecipeManager RecipeManager { get; }

        protected RecipeViewModelBase(RecipeManager recipeManager)
        {
            RecipeManager = recipeManager;
        }

        protected abstract TRecipe CurrentRecipe { get; set; }
        protected abstract string RecipeFolderPath { get; }
        protected abstract string EmptyRecipeDisplayName { get; }
        protected abstract string GetActiveRecipeName();
        protected abstract void SwitchActiveRecipe(string recipeName);
        protected abstract void ClearActiveRecipe();
        protected virtual void OnRecipeLoaded(TRecipe recipe, RecipeWrap recipeWrap) { }
        protected virtual string GetUnsavedChangesMessage() => $"配方“{CurrentRecipeDisplayName}”有未保存修改，是否先保存？";
        protected virtual void AfterDeleteCurrentRecipe(string deletedRecipeName) { }
        protected virtual void AfterRecipeRenamed(string oldRecipeName, string newRecipeName) { }

        public virtual bool TryLeaveCurrentRecipeContext()
        {
            if (!HasUnsavedChanges)
                return true;

            var result = MwMessageBox.Show(GetUnsavedChangesMessage(), "未保存提示", System.Windows.MessageBoxButton.YesNoCancel, System.Windows.MessageBoxImage.Warning);
            if (result == System.Windows.MessageBoxResult.Cancel)
                return false;

            if (result == System.Windows.MessageBoxResult.Yes)
                SaveCurrentRecipe(false);

            return true;
        }

        public void SaveCurrentRecipeSilently()
        {
            SaveCurrentRecipe(false);
        }

        protected void RegisterButtonGroupEvents()
        {
            RecipeButtonGroupViewModel = new RecipeButtonGroupViewModel();
            RecipeButtonGroupViewModel.CreateNewRequested -= OnCreateNewRecipe;
            RecipeButtonGroupViewModel.ApplyRequested -= OnApplyRecipe;
            RecipeButtonGroupViewModel.DeleteRequested -= OnDeleteRecipe;
            RecipeButtonGroupViewModel.CopyRequested -= OnCopyRecipe;
            RecipeButtonGroupViewModel.ImportRequested -= OnImportRecipe;
            RecipeButtonGroupViewModel.SaveRequested -= OnSaveRecipe;
            RecipeButtonGroupViewModel.ExportRequested -= OnExportRecipe;
            RecipeButtonGroupViewModel.ReNameRequested -= OnReNameRecipe;
            RecipeButtonGroupViewModel.ClearRequested -= OnClearRecipe;
            RecipeButtonGroupViewModel.CreateNewRequested += OnCreateNewRecipe;
            RecipeButtonGroupViewModel.ApplyRequested += OnApplyRecipe;
            RecipeButtonGroupViewModel.DeleteRequested += OnDeleteRecipe;
            RecipeButtonGroupViewModel.CopyRequested += OnCopyRecipe;
            RecipeButtonGroupViewModel.ImportRequested += OnImportRecipe;
            RecipeButtonGroupViewModel.SaveRequested += OnSaveRecipe;
            RecipeButtonGroupViewModel.ExportRequested += OnExportRecipe;
            RecipeButtonGroupViewModel.ReNameRequested += OnReNameRecipe;
            RecipeButtonGroupViewModel.ClearRequested += OnClearRecipe;
        }

        public virtual void OnCreateNewRecipe(object sender, EventArgs eventArgs)
        {
            if (eventArgs is RecipeEventArgs args)
            {
                if (!TryLeaveCurrentRecipeContext())
                    return;

                var recipeWrap = new RecipeWrap
                {
                    RecipeName = args.RecipeName,
                    CreateTime = DateTime.UtcNow,
                    ModifiedTime = DateTime.UtcNow,
                };

                RecipeWraps.Add(recipeWrap);
                SelectRecipeWrapInternal(recipeWrap);
            }
        }

        public virtual void OnDeleteRecipe(object sender, EventArgs eventArgs)
        {
            if (SelectedRecipeWrap == null)
                return;

            if (!TryLeaveCurrentRecipeContext())
                return;

            var deletedRecipeName = SelectedRecipeWrap.RecipeName;
            if (!RecipeManager.DeleteRecipeFolder(RecipeFolderPath, deletedRecipeName, out var error))
            {
                MwMessageBox.Show(error);
                return;
            }

            RecipeWraps.Remove(SelectedRecipeWrap);
            AfterDeleteCurrentRecipe(deletedRecipeName);

            if (RecipeWraps.Count > 0)
            {
                SelectRecipeWrapInternal(RecipeWraps[0]);
            }
            else
            {
                SelectedRecipeWrap = null;
                CurrentRecipe = null;
                ClearActiveRecipe();
                CaptureSavedSnapshot();
            }
        }

        public virtual void OnCopyRecipe(object sender, EventArgs eventArgs)
        {
            if (!(eventArgs is RecipeEventArgs args) || SelectedRecipeWrap == null)
                return;

            var sourceRecipeName = SelectedRecipeWrap.RecipeName;
            if (string.IsNullOrWhiteSpace(sourceRecipeName) || string.IsNullOrWhiteSpace(args.RecipeName))
                return;

            if (CurrentRecipe != null &&
                string.Equals(CurrentRecipe.RecipeName, sourceRecipeName, StringComparison.Ordinal) &&
                HasUnsavedChanges)
            {
                SaveCurrentRecipe(false);
            }

            if (!RecipeManager.CopyRecipeFolder(RecipeFolderPath, sourceRecipeName, args.RecipeName, out var error))
            {
                MwMessageBox.Show(error);
                return;
            }

            var copiedRecipeWrap = new RecipeWrap
            {
                RecipeName = args.RecipeName,
                CreateTime = DateTime.UtcNow,
                ModifiedTime = DateTime.UtcNow,
            };

            RecipeWraps.Add(copiedRecipeWrap);
            SelectRecipeWrapInternal(copiedRecipeWrap);
        }

        public virtual void OnApplyRecipe(object sender, EventArgs eventArgs)
        {
            if (eventArgs is RecipeEventArgs args)
            {
                var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.RecipeName);
                if (recipeWrap == null)
                    return;

                if (!ReferenceEquals(SelectedRecipeWrap, recipeWrap))
                {
                    if (!TryLeaveCurrentRecipeContext())
                        return;

                    SelectRecipeWrapInternal(recipeWrap);
                }

                if (SelectedRecipeWrap == null)
                    return;

                if (CurrentRecipe != null && IsRecipeDirty())
                {
                    SaveCurrentRecipe(false);
                }

                SwitchActiveRecipe(SelectedRecipeWrap.RecipeName);
                UpdateInUseState(SelectedRecipeWrap);
            }
        }

        public abstract void OnImportRecipe(object sender, EventArgs eventArgs);

        public virtual void OnSaveRecipe(object sender, EventArgs eventArgs)
        {
            CommonUti.RunOnUi(() =>
            {
                if (eventArgs is RecipeEventArgs)
                {
                    SaveCurrentRecipe(true);
                }
            });
        }

        public abstract void OnExportRecipe(object sender, EventArgs eventArgs);
        public abstract void OnReNameRecipe(object sender, EventArgs eventArgs);

        public virtual void OnClearRecipe(object sender, EventArgs eventArgs)
        {
            if (!TryLeaveCurrentRecipeContext())
                return;

            if (SelectedRecipeWrap == null)
                return;

            var clearedRecipe = new TRecipe
            {
                RecipeName = SelectedRecipeWrap.RecipeName
            };

            CurrentRecipe = clearedRecipe;
            OnRecipeLoaded(clearedRecipe, SelectedRecipeWrap);
            NotifyOfPropertyChange(() => HasUnsavedChanges);
        }

        protected bool IsRecipeDirty()
        {
            if (CurrentRecipe == null)
                return false;

            var path = Path.Combine(CurrentRecipe.Dir ?? string.Empty, CurrentRecipe.FileName ?? string.Empty);
            if (!File.Exists(path))
                return true;

            return !AreSnapshotsEquivalent(_lastSavedSnapshot, CurrentRecipe.CreateSnapshot());
        }

        protected void CaptureSavedSnapshot()
        {
            _lastSavedSnapshot = CurrentRecipe != null ? CurrentRecipe.CreateSnapshot() : string.Empty;
            NotifyOfPropertyChange(() => HasUnsavedChanges);
            NotifyOfPropertyChange(() => CurrentRecipeDisplayName);
        }

        protected bool TryRenameRecipe(RecipeRenameEventArgs args)
        {
            if (args == null)
                return false;

            var recipeWrap = RecipeWraps.FirstOrDefault(p => p.RecipeName == args.OldRecipeName);
            if (recipeWrap == null)
                return false;

            if (!RecipeManager.RenameFolder(RecipeFolderPath, args.OldRecipeName, args.NewRecipeName, out var error))
            {
                MwMessageBox.Show(error);
                return false;
            }

            recipeWrap.RecipeName = args.NewRecipeName;
            recipeWrap.ModifiedTime = DateTime.UtcNow;

            if (CurrentRecipe != null && string.Equals(CurrentRecipe.RecipeName, args.OldRecipeName, StringComparison.Ordinal))
            {
                CurrentRecipe.RecipeName = args.NewRecipeName;
            }

            if (ReferenceEquals(recipeWrap, SelectedRecipeWrap))
            {
                SwitchActiveRecipe(args.NewRecipeName);
            }

            CaptureSavedSnapshot();
            AfterRecipeRenamed(args.OldRecipeName, args.NewRecipeName);
            return true;
        }

        protected void SaveCurrentRecipe(bool showSuccessMessage)
        {
            if (CurrentRecipe == null)
                return;

            CurrentRecipe.Write();
            CaptureSavedSnapshot();

            if (SelectedRecipeWrap != null)
            {
                SelectedRecipeWrap.ModifiedTime = DateTime.UtcNow;
            }

            if (showSuccessMessage)
            {
                MwMessageBox.Show("保存成功");
            }
        }

        private static bool AreSnapshotsEquivalent(string leftSnapshot, string rightSnapshot)
        {
            var left = string.IsNullOrWhiteSpace(leftSnapshot) ? null : ParseSnapshotToken(leftSnapshot);
            var right = string.IsNullOrWhiteSpace(rightSnapshot) ? null : ParseSnapshotToken(rightSnapshot);

            if (left == null || right == null)
                return false;

            return JToken.DeepEquals(left, right);
        }

        private static JToken ParseSnapshotToken(string snapshot)
        {
            try
            {
                return JToken.Parse(snapshot);
            }
            catch
            {
                return null;
            }
        }

        protected void SelectRecipeWrapInternal(RecipeWrap recipeWrap)
        {
            try
            {
                _skipLeaveCheck = true;
                SelectedRecipeWrap = recipeWrap;
            }
            finally
            {
                _skipLeaveCheck = false;
            }
        }

        protected void InitializeSelection(string activeRecipeName)
        {
            SyncSelection(activeRecipeName, true);
        }

        protected virtual void UpdateInUseState(RecipeWrap activeRecipeWrap)
        {
            foreach (var wrap in RecipeWraps)
            {
                wrap.IsInUse = ReferenceEquals(wrap, activeRecipeWrap);
            }
        }

        public void SyncSelectionFromActiveRecipe()
        {
            SyncSelection(GetActiveRecipeName(), false);
        }

        private void SyncSelection(string recipeName, bool fallbackToFirst)
        {
            if (RecipeWraps == null || RecipeWraps.Count == 0)
            {
                UpdateInUseState(null);
                return;
            }

            var target = string.IsNullOrWhiteSpace(recipeName)
                ? null
                : RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, recipeName, StringComparison.Ordinal));

            if (target == null)
            {
                UpdateInUseState(null);
                if (!fallbackToFirst)
                    return;

                target = RecipeWraps[0];
            }

            if (ReferenceEquals(SelectedRecipeWrap, target))
            {
                UpdateInUseState(target);
                return;
            }

            SelectRecipeWrapInternal(target);
        }

        private void LoadRecipeFromWrap(RecipeWrap recipeWrap)
        {
            if (recipeWrap == null)
            {
                UpdateInUseStateByActiveRecipe();
                return;
            }

            var newRecipe = new TRecipe
            {
                RecipeName = recipeWrap.RecipeName
            };
            newRecipe.Read();

            CurrentRecipe = newRecipe;
            CaptureSavedSnapshot();
            UpdateInUseStateByActiveRecipe();
            OnRecipeLoaded(newRecipe, recipeWrap);
        }

        private void UpdateInUseStateByActiveRecipe()
        {
            var activeRecipeName = GetActiveRecipeName();
            var activeRecipeWrap = RecipeWraps == null || string.IsNullOrWhiteSpace(activeRecipeName)
                ? null
                : RecipeWraps.FirstOrDefault(p => string.Equals(p.RecipeName, activeRecipeName, StringComparison.Ordinal));

            UpdateInUseState(activeRecipeWrap);
        }
    }
}
