using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;

namespace XLAB
{
    internal sealed class EkzDirectoryWindowViewModel : ObservableObject
    {
        private readonly IEkzDirectoryDialogService _dialogService;
        private readonly EkzDirectoryService _service;
        private List<EkzDirectoryItem> _ekzCache;
        private bool _isApplyingFilter;
        private bool _isBusy;
        private string _searchText;
        private EkzDirectoryItem _selectedEkz;
        private int _selectedOwnerFilterId;
        private string _statusText;

        public EkzDirectoryWindowViewModel(EkzDirectoryService service, IEkzDirectoryDialogService dialogService)
        {
            _service = service;
            _dialogService = dialogService;
            _ekzCache = new List<EkzDirectoryItem>();

            EkzItems = new ObservableCollection<EkzDirectoryItem>();
            EkzMkItems = new ObservableCollection<EkzMkDirectoryItem>();
            OwnerFilterItems = new ObservableCollection<DirectoryLookupItem>();
            OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });

            AddEkzCommand = new RelayCommand(delegate { AddEkzAsync(); }, delegate { return !IsBusy; });
            EditEkzCommand = new RelayCommand(delegate { EditEkzAsync(); }, delegate { return !IsBusy && SelectedEkz != null; });
            DeleteEkzCommand = new RelayCommand(delegate { DeleteEkzWithPreviewAsync(); }, delegate { return !IsBusy && SelectedEkz != null; });
            RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });

            UpdateStatus();
        }

        public ICommand AddEkzCommand { get; private set; }

        public ICommand DeleteEkzCommand { get; private set; }

        public ICommand EditEkzCommand { get; private set; }

        public ObservableCollection<EkzDirectoryItem> EkzItems { get; private set; }

        public ObservableCollection<EkzMkDirectoryItem> EkzMkItems { get; private set; }

        public bool IsBusy
        {
            get { return _isBusy; }
            private set
            {
                if (SetProperty(ref _isBusy, value))
                {
                    RaiseCommandStates();
                }
            }
        }

        public ObservableCollection<DirectoryLookupItem> OwnerFilterItems { get; private set; }

        public ICommand RefreshCommand { get; private set; }

        public string SearchText
        {
            get { return _searchText; }
            set
            {
                if (SetProperty(ref _searchText, value))
                {
                    ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id);
                }
            }
        }

        public EkzDirectoryItem SelectedEkz
        {
            get { return _selectedEkz; }
            set
            {
                if (SetProperty(ref _selectedEkz, value))
                {
                    RaiseCommandStates();

                    if (!_isApplyingFilter)
                    {
                        LoadEkzMkForSelection();
                    }

                    UpdateStatus();
                }
            }
        }

        public int SelectedOwnerFilterId
        {
            get { return _selectedOwnerFilterId; }
            set
            {
                if (SetProperty(ref _selectedOwnerFilterId, value))
                {
                    ApplyFilter(SelectedEkz == null ? (int?)null : SelectedEkz.Id);
                }
            }
        }

        public string StatusText
        {
            get { return _statusText; }
            private set { SetProperty(ref _statusText, value); }
        }

        public async Task InitializeAsync()
        {
            await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(null); });
        }

        private void AddEkzAsync()
        {
            var result = _dialogService.ShowEkzEditDialog(new EkzDirectoryItem(), true, _ekzCache.ToList(), _service);
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var createdId = await Task.Run(delegate { return _service.AddEkzItem(result); });
                await RefreshCoreAsync(createdId);
                _dialogService.ShowInfo("Запись EKZ добавлена.");
            });
        }

        private void ApplyFilter(int? preferredId)
        {
            var filteredItems = _ekzCache.Where(delegate(EkzDirectoryItem item)
            {
                return MatchesOwnerFilter(item) && MatchesSearch(item);
            }).ToList();

            _isApplyingFilter = true;
            try
            {
                EkzItems.Clear();
                foreach (var item in filteredItems)
                {
                    EkzItems.Add(item);
                }

                SelectedEkz = preferredId.HasValue
                    ? EkzItems.FirstOrDefault(delegate(EkzDirectoryItem item) { return item.Id == preferredId.Value; })
                    : EkzItems.FirstOrDefault();
            }
            finally
            {
                _isApplyingFilter = false;
            }

            if (!IsBusy)
            {
                LoadEkzMkForSelection();
            }

            UpdateStatus();
        }

        private static EkzDirectoryItem CloneEkz(EkzDirectoryItem source)
        {
            return new EkzDirectoryItem
            {
                Id = source.Id,
                TypeSizeId = source.TypeSizeId,
                MeasurementAreaName = source.MeasurementAreaName,
                InstrumentName = source.InstrumentName,
                TypeName = source.TypeName,
                RangeText = source.RangeText,
                AccuracyText = source.AccuracyText,
                RegistryNumber = source.RegistryNumber,
                OwnerOrganizationId = source.OwnerOrganizationId,
                OwnerOrganizationName = source.OwnerOrganizationName,
                SerialNumber = source.SerialNumber,
                InventoryNumber = source.InventoryNumber,
                Notes = source.Notes
            };
        }

        private async void DeleteEkzWithPreviewAsync()
        {
            if (SelectedEkz == null)
            {
                return;
            }

            var selected = SelectedEkz;
            EkzDeletePreview preview;

            try
            {
                IsBusy = true;
                preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); });
            }
            catch (InvalidOperationException ex)
            {
                _dialogService.ShowWarning(ex.Message);
                return;
            }
            catch (Exception ex)
            {
                _dialogService.ShowError(ex.Message);
                return;
            }
            finally
            {
                IsBusy = false;
            }

            if (preview == null)
            {
                return;
            }

            if (!preview.CanDelete)
            {
                _dialogService.ShowWarning(preview.WarningMessage);
                return;
            }

            if (!_dialogService.Confirm(BuildDeleteConfirmationMessage(selected, preview)))
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); });
                if (!result.IsDeleted)
                {
                    _dialogService.ShowWarning(result.WarningMessage);
                    return;
                }

                await RefreshCoreAsync(null);
                _dialogService.ShowInfo(BuildDeleteResultMessage(result));
            });
        }

        private async void DeleteEkzAsync()
        {
            if (SelectedEkz == null)
            {
                return;
            }

            var selected = SelectedEkz;
            EkzDeletePreview preview;

            try
            {
                IsBusy = true;
                preview = await Task.Run(delegate { return _service.GetEkzDeletePreview(selected.Id); });
            }
            catch (InvalidOperationException ex)
            {
                _dialogService.ShowWarning(ex.Message);
                return;
            }
            catch (Exception ex)
            {
                _dialogService.ShowError(ex.Message);
                return;
            }
            finally
            {
                IsBusy = false;
            }

            if (preview == null)
            {
                return;
            }

            if (!preview.CanDelete)
            {
                _dialogService.ShowWarning(preview.WarningMessage);
                return;
            }
            if (!_dialogService.Confirm(string.Format("Удалить экземпляр \"{0}\"?", selected.SerialNumber)))
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                var result = await Task.Run(delegate { return _service.DeleteEkzItem(selected.Id); });
                if (!result.IsDeleted)
                {
                    _dialogService.ShowWarning(result.WarningMessage);
                    return;
                }

                await RefreshCoreAsync(null);
                _dialogService.ShowInfo("Запись EKZ удалена.");
            });
        }

        private void EditEkzAsync()
        {
            if (SelectedEkz == null)
            {
                return;
            }

            var result = _dialogService.ShowEkzEditDialog(CloneEkz(SelectedEkz), false, _ekzCache.ToList(), _service);
            if (result == null)
            {
                return;
            }

            RunMutationOperation(async delegate
            {
                await Task.Run(delegate { _service.UpdateEkzItem(result); });
                await RefreshCoreAsync(result.Id);
                _dialogService.ShowInfo("Запись EKZ обновлена.");
            });
        }

        private async Task ExecuteBusyOperationAsync(Func<Task> operation)
        {
            try
            {
                IsBusy = true;
                await operation();
            }
            catch (Exception ex)
            {
                _dialogService.ShowError(ex.Message);
            }
            finally
            {
                IsBusy = false;
            }
        }

        private async Task ExecuteMutationOperationAsync(Func<Task> operation)
        {
            try
            {
                IsBusy = true;
                await operation();
            }
            catch (InvalidOperationException ex)
            {
                _dialogService.ShowWarning(ex.Message);
            }
            catch (Exception ex)
            {
                _dialogService.ShowError(ex.Message);
            }
            finally
            {
                IsBusy = false;
            }
        }

        private string[] GetSearchTokens()
        {
            return (SearchText ?? string.Empty)
                .Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
                .Select(delegate(string token) { return token.Trim().ToUpperInvariant(); })
                .Where(delegate(string token) { return token.Length > 0; })
                .ToArray();
        }

        private void LoadEkzMkForSelection()
        {
            if (IsBusy)
            {
                return;
            }

            RunBusyOperation(async delegate
            {
                if (SelectedEkz == null)
                {
                    EkzMkItems.Clear();
                    UpdateStatus();
                    return;
                }

                await RefreshEkzMkCoreAsync(SelectedEkz.Id);
            });
        }

        private bool MatchesOwnerFilter(EkzDirectoryItem item)
        {
            return SelectedOwnerFilterId <= 0
                   || (item != null && item.OwnerOrganizationId == SelectedOwnerFilterId);
        }

        private bool MatchesSearch(EkzDirectoryItem item)
        {
            var tokens = GetSearchTokens();
            if (tokens.Length == 0)
            {
                return true;
            }

            var haystack = string.Join(
                    " ",
                    new[]
                    {
                        item == null ? null : item.Id.ToString(),
                        item == null ? null : item.OwnerOrganizationName,
                        item == null ? null : item.MeasurementAreaName,
                        item == null ? null : item.InstrumentName,
                        item == null ? null : item.TypeName,
                        item == null ? null : item.RangeText,
                        item == null ? null : item.AccuracyText,
                        item == null ? null : item.RegistryNumber,
                        item == null ? null : item.SerialNumber,
                        item == null ? null : item.InventoryNumber,
                        item == null ? null : item.Notes
                    }.Where(delegate(string value) { return !string.IsNullOrWhiteSpace(value); }))
                .ToUpperInvariant();

            return tokens.All(delegate(string token) { return haystack.IndexOf(token, StringComparison.Ordinal) >= 0; });
        }

        private async Task RefreshCoreAsync(int? idToSelect)
        {
            var currentSelectedId = idToSelect ?? (SelectedEkz == null ? (int?)null : SelectedEkz.Id);
            var currentOwnerFilterId = SelectedOwnerFilterId;

            var ekzTask = Task.Run(delegate { return _service.LoadEkzItems(); });
            var ownerTask = Task.Run(delegate { return _service.LoadFrpdReferences(); });
            await Task.WhenAll(ekzTask, ownerTask);

            _ekzCache = ekzTask.Result.ToList();
            RebuildOwnerFilters(ownerTask.Result, currentOwnerFilterId);

            ApplyFilter(currentSelectedId);
            if (SelectedEkz == null)
            {
                EkzMkItems.Clear();
                UpdateStatus();
                return;
            }

            await RefreshEkzMkCoreAsync(SelectedEkz.Id);
            UpdateStatus();
        }

        private async Task RefreshEkzMkCoreAsync(int instrumentId)
        {
            var items = await Task.Run(delegate { return _service.LoadEkzMkItems(instrumentId); });
            EkzMkItems.Clear();
            foreach (var item in items)
            {
                EkzMkItems.Add(item);
            }
        }

        private void RebuildOwnerFilters(IReadOnlyList<DirectoryLookupItem> owners, int selectedId)
        {
            OwnerFilterItems.Clear();
            OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });

            foreach (var owner in owners ?? Array.Empty<DirectoryLookupItem>())
            {
                OwnerFilterItems.Add(owner);
            }

            _selectedOwnerFilterId = OwnerFilterItems.Any(delegate(DirectoryLookupItem item) { return item.Id == selectedId; })
                ? selectedId
                : 0;
            OnPropertyChanged("SelectedOwnerFilterId");
        }

        private void RefreshAsync()
        {
            RunBusyOperation(delegate { return RefreshCoreAsync(SelectedEkz == null ? (int?)null : SelectedEkz.Id); });
        }

        private void RaiseCommandStates()
        {
            ((RelayCommand)AddEkzCommand).RaiseCanExecuteChanged();
            ((RelayCommand)EditEkzCommand).RaiseCanExecuteChanged();
            ((RelayCommand)DeleteEkzCommand).RaiseCanExecuteChanged();
            ((RelayCommand)RefreshCommand).RaiseCanExecuteChanged();
        }

        private async void RunBusyOperation(Func<Task> operation)
        {
            try
            {
                await ExecuteBusyOperationAsync(operation);
            }
            catch (Exception ex)
            {
                IsBusy = false;
                _dialogService.ShowError(ex.Message);
            }
        }

        private async void RunMutationOperation(Func<Task> operation)
        {
            try
            {
                await ExecuteMutationOperationAsync(operation);
            }
            catch (Exception ex)
            {
                IsBusy = false;
                _dialogService.ShowError(ex.Message);
            }
        }

        private static string BuildDeleteConfirmationMessage(EkzDirectoryItem selected, EkzDeletePreview preview)
        {
            return string.Format(
                "Удалить экземпляр \"{0}\"?{1}{1}{2}",
                selected == null || string.IsNullOrWhiteSpace(selected.SerialNumber) ? "(без номера)" : selected.SerialNumber,
                Environment.NewLine,
                preview == null ? string.Empty : preview.ConfirmationMessage ?? string.Empty);
        }

        private static string BuildDeleteResultMessage(EkzDeleteResult result)
        {
            var impacts = result == null || result.ImpactItems == null
                ? new List<EkzDeleteImpactItem>()
                : result.ImpactItems.Where(delegate(EkzDeleteImpactItem item) { return item != null && item.RowCount > 0; }).ToList();

            if (impacts.Count == 0)
            {
                return "Запись EKZ удалена.";
            }

            return "Удалены записи: " + string.Join(", ", impacts.Select(delegate(EkzDeleteImpactItem item)
            {
                return string.Format("{0}: {1}", item.TableName, item.RowCount);
            })) + ".";
        }

        private void UpdateStatus()
        {
            var searchPrefix = string.IsNullOrWhiteSpace(SearchText)
                ? string.Empty
                : string.Format("Поиск: \"{0}\". ", SearchText.Trim());
            var ownerName = OwnerFilterItems.FirstOrDefault(delegate(DirectoryLookupItem item) { return item.Id == SelectedOwnerFilterId; });
            var ownerPrefix = SelectedOwnerFilterId <= 0 || ownerName == null
                ? string.Empty
                : string.Format("Владелец: \"{0}\". ", ownerName.Name);

            StatusText = string.Format(
                "{0}{1}EKZ: {2}/{3}. EKZMK: {4}.",
                searchPrefix,
                ownerPrefix,
                EkzItems.Count,
                _ekzCache.Count,
                EkzMkItems.Count);
        }
    }
}
