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 PlanningWindowViewModel : ObservableObject
    {
        private readonly IPlanningDialogService _dialogService;
        private readonly EkzDirectoryService _ekzService;
        private readonly PlanningService _service;
        private List<EkzDirectoryItem> _ekzCache;
        private bool _isBusy;
        private List<PlanningItem> _planCache;
        private string _searchText;
        private PlanningItem _selectedItem;
        private int _selectedOwnerFilterId;
        private int _selectedYear;
        private string _statusText;

        public PlanningWindowViewModel(PlanningService service, EkzDirectoryService ekzService, IPlanningDialogService dialogService)
        {
            _service = service;
            _ekzService = ekzService;
            _dialogService = dialogService;
            _planCache = new List<PlanningItem>();
            _ekzCache = new List<EkzDirectoryItem>();

            PlanItems = new ObservableCollection<PlanningItem>();
            OwnerFilterItems = new ObservableCollection<DirectoryLookupItem>();
            YearItems = new ObservableCollection<int>();

            var currentYear = DateTime.Today.Year;
            for (var year = currentYear; year <= currentYear + 5; year++)
            {
                YearItems.Add(year);
            }

            _selectedYear = currentYear + 1;

            OwnerFilterItems.Add(new DirectoryLookupItem { Id = 0, Name = "Все организации" });

            AddInstrumentCommand = new RelayCommand(delegate { AddInstrumentAsync(); }, delegate { return !IsBusy; });
            EditInstrumentCommand = new RelayCommand(delegate { EditInstrumentAsync(); }, delegate { return !IsBusy && SelectedItem != null; });
            AddPlanCommand = new RelayCommand(delegate { AddPlanAsync(); }, delegate { return !IsBusy && _ekzCache.Count > 0; });
            EditPlanCommand = new RelayCommand(delegate { EditPlanAsync(); }, delegate { return !IsBusy && SelectedItem != null && SelectedItem.CanPersistPlan; });
            DeletePlanCommand = new RelayCommand(delegate { DeletePlanAsync(); }, delegate { return !IsBusy && SelectedItem != null && SelectedItem.PlanId.HasValue; });
            RefreshCommand = new RelayCommand(delegate { RefreshAsync(); }, delegate { return !IsBusy; });

            UpdateStatus();
        }

        public ICommand AddInstrumentCommand { get; private set; }

        public ICommand AddPlanCommand { get; private set; }

        public ICommand DeletePlanCommand { get; private set; }

        public ICommand EditInstrumentCommand { get; private set; }

        public ICommand EditPlanCommand { 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 ObservableCollection<PlanningItem> PlanItems { get; private set; }

        public ICommand RefreshCommand { get; private set; }

        public string SearchText
        {
            get { return _searchText; }
            set
            {
                if (SetProperty(ref _searchText, value))
                {
                    ApplyFilter(GetPreferredRowKey(SelectedItem));
                }
            }
        }

        public PlanningItem SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (SetProperty(ref _selectedItem, value))
                {
                    RaiseCommandStates();
                    UpdateStatus();
                }
            }
        }

        public int SelectedOwnerFilterId
        {
            get { return _selectedOwnerFilterId; }
            set
            {
                if (SetProperty(ref _selectedOwnerFilterId, value))
                {
                    ApplyFilter(GetPreferredRowKey(SelectedItem));
                }
            }
        }

        public int SelectedYear
        {
            get { return _selectedYear; }
            set
            {
                if (SetProperty(ref _selectedYear, value) && !IsBusy)
                {
                    RefreshAsync();
                }
            }
        }

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

        public ObservableCollection<int> YearItems { get; private set; }

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

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

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

        private async void AddPlanAsync()
        {
            if (_ekzCache.Count == 0)
            {
                _dialogService.ShowWarning("Список приборов EKZ пуст.");
                return;
            }

            var seed = new PlanningEditSeed
            {
                TargetYear = SelectedYear,
                InstrumentId = SelectedItem == null ? 0 : SelectedItem.InstrumentId,
                TemplateId = SelectedItem == null ? (int?)null : SelectedItem.EffectiveTemplateId,
                PlannedOn = SelectedItem != null && SelectedItem.PlannedOn.HasValue && SelectedItem.PlannedOn.Value.Year == SelectedYear
                    ? SelectedItem.PlannedOn
                    : (DateTime?)null
            };

            var result = _dialogService.ShowPlanningEditDialog(seed, true, BuildInstrumentOptions(), _service);
            if (result == null)
            {
                return;
            }

            await RunMutationOperation(async delegate
            {
                var createdId = await Task.Run(delegate { return _service.AddPlanItem(result); });
                await RefreshCoreAsync(CreatePreferredRowKey(result.InstrumentId, createdId));
                _dialogService.ShowInfo("Плановая запись EKZMCP добавлена.");
            });
        }

        private void ApplyFilter(string preferredKey)
        {
            var filteredItems = _planCache.Where(delegate(PlanningItem item)
            {
                return MatchesOwnerFilter(item) && MatchesSearch(item);
            }).ToList();

            PlanItems.Clear();
            foreach (var item in filteredItems)
            {
                PlanItems.Add(item);
            }

            SelectedItem = string.IsNullOrWhiteSpace(preferredKey)
                ? PlanItems.FirstOrDefault()
                : PlanItems.FirstOrDefault(delegate(PlanningItem item) { return string.Equals(GetPreferredRowKey(item), preferredKey, StringComparison.OrdinalIgnoreCase); })
                  ?? PlanItems.FirstOrDefault();

            UpdateStatus();
        }

        private List<PlanningInstrumentOption> BuildInstrumentOptions()
        {
            return _ekzCache
                .Select(delegate(EkzDirectoryItem item)
                {
                    var tail = string.IsNullOrWhiteSpace(item.InventoryNumber)
                        ? string.Empty
                        : string.Format(" / инв.№ {0}", item.InventoryNumber);

                    return new PlanningInstrumentOption
                    {
                        Id = item.Id,
                        TypeSizeId = item.TypeSizeId,
                        OwnerOrganizationId = item.OwnerOrganizationId,
                        DisplayName = string.Format(
                            "{0} / {1} / {2} / {3} / {4} / зав.№ {5}{6}",
                            item.OwnerOrganizationName,
                            item.MeasurementAreaName,
                            item.InstrumentName,
                            item.TypeName,
                            item.RangeText,
                            item.SerialNumber,
                            tail)
                    };
                })
                .OrderBy(delegate(PlanningInstrumentOption item) { return item.DisplayName; }, StringComparer.CurrentCultureIgnoreCase)
                .ToList();
        }

        private static string CloneValue(string value)
        {
            return value == null ? string.Empty : string.Copy(value);
        }

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

        private static string CreatePreferredRowKey(int instrumentId, int? planId)
        {
            return string.Format(
                "{0}:{1}",
                instrumentId,
                planId.HasValue ? planId.Value.ToString() : "0");
        }

        private async void DeletePlanAsync()
        {
            if (SelectedItem == null || !SelectedItem.PlanId.HasValue)
            {
                return;
            }

            var selected = SelectedItem;
            var plannedDateText = selected.PlannedOn.HasValue
                ? selected.PlannedOn.Value.ToString("d")
                : "без даты";
            var question = string.Format(
                "Удалить плановую запись EKZMCP для прибора \"{0}\" с датой {1}?",
                selected.SerialNumber,
                plannedDateText);

            if (!_dialogService.Confirm(question))
            {
                return;
            }

            await RunMutationOperation(async delegate
            {
                await Task.Run(delegate { _service.DeletePlanItem(selected.PlanId.Value); });
                await RefreshCoreAsync(CreatePreferredRowKey(selected.InstrumentId, null));
                _dialogService.ShowInfo("Плановая запись EKZMCP удалена.");
            });
        }

        private async void EditInstrumentAsync()
        {
            if (SelectedItem == null)
            {
                return;
            }

            var current = _ekzCache.FirstOrDefault(delegate(EkzDirectoryItem item) { return item.Id == SelectedItem.InstrumentId; });
            if (current == null)
            {
                _dialogService.ShowWarning("Выбранный прибор не найден в EKZ.");
                return;
            }

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

            var preferredKey = GetPreferredRowKey(SelectedItem);

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

        private async void EditPlanAsync()
        {
            if (SelectedItem == null)
            {
                return;
            }

            if (!SelectedItem.CanPersistPlan)
            {
                _dialogService.ShowWarning("Для выбранного прибора найден только расчетный период без TPRMCP. Сохранить запись в EKZMCP нельзя.");
                return;
            }

            var seed = new PlanningEditSeed
            {
                TargetYear = SelectedYear,
                PlanId = SelectedItem.PlanId,
                InstrumentId = SelectedItem.InstrumentId,
                TemplateId = SelectedItem.EffectiveTemplateId,
                PlannedOn = SelectedItem.PlannedOn
            };

            var isNew = !SelectedItem.PlanId.HasValue;
            var result = _dialogService.ShowPlanningEditDialog(seed, isNew, BuildInstrumentOptions(), _service);
            if (result == null)
            {
                return;
            }

            await RunMutationOperation(async delegate
            {
                if (result.PlanId.HasValue)
                {
                    await Task.Run(delegate { _service.UpdatePlanItem(result); });
                    await RefreshCoreAsync(CreatePreferredRowKey(result.InstrumentId, result.PlanId));
                    _dialogService.ShowInfo("Плановая запись EKZMCP обновлена.");
                }
                else
                {
                    var createdId = await Task.Run(delegate { return _service.AddPlanItem(result); });
                    await RefreshCoreAsync(CreatePreferredRowKey(result.InstrumentId, createdId));
                    _dialogService.ShowInfo("Плановая запись EKZMCP добавлена.");
                }
            });
        }

        private async Task ExecuteBusyOperationAsync(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 static string GetPreferredRowKey(PlanningItem item)
        {
            return item == null ? null : CreatePreferredRowKey(item.InstrumentId, item.PlanId);
        }

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

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

            var haystack = string.Join(
                    " ",
                    new[]
                    {
                        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.RegistryNumber,
                        item == null ? null : item.SerialNumber,
                        item == null ? null : item.InventoryNumber,
                        item == null ? null : item.PlanSource,
                        item == null ? null : item.PeriodSource
                    })
                .ToUpperInvariant();

            return tokens.All(delegate(string token) { return haystack.Contains(token); });
        }

        private void RaiseCommandStates()
        {
            var addInstrumentCommand = AddInstrumentCommand as RelayCommand;
            if (addInstrumentCommand != null)
            {
                addInstrumentCommand.RaiseCanExecuteChanged();
            }

            var editInstrumentCommand = EditInstrumentCommand as RelayCommand;
            if (editInstrumentCommand != null)
            {
                editInstrumentCommand.RaiseCanExecuteChanged();
            }

            var addPlanCommand = AddPlanCommand as RelayCommand;
            if (addPlanCommand != null)
            {
                addPlanCommand.RaiseCanExecuteChanged();
            }

            var editPlanCommand = EditPlanCommand as RelayCommand;
            if (editPlanCommand != null)
            {
                editPlanCommand.RaiseCanExecuteChanged();
            }

            var deletePlanCommand = DeletePlanCommand as RelayCommand;
            if (deletePlanCommand != null)
            {
                deletePlanCommand.RaiseCanExecuteChanged();
            }

            var refreshCommand = RefreshCommand as RelayCommand;
            if (refreshCommand != null)
            {
                refreshCommand.RaiseCanExecuteChanged();
            }
        }

        private async Task RefreshCoreAsync(string preferredKey)
        {
            var planTask = Task.Run(delegate { return _service.LoadPlanItems(SelectedYear); });
            var ownerTask = Task.Run(delegate { return _service.LoadOwnerItems(); });
            var ekzTask = Task.Run(delegate { return _ekzService.LoadEkzItems(); });

            await Task.WhenAll(planTask, ownerTask, ekzTask);

            _planCache = planTask.Result.ToList();
            _ekzCache = ekzTask.Result.ToList();

            ApplyOwnerItems(ownerTask.Result);
            ApplyFilter(preferredKey);
        }

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

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

            if (!OwnerFilterItems.Any(delegate(DirectoryLookupItem item) { return item.Id == SelectedOwnerFilterId; }))
            {
                SelectedOwnerFilterId = 0;
            }
        }

        private async void RefreshAsync()
        {
            await ExecuteBusyOperationAsync(delegate { return RefreshCoreAsync(GetPreferredRowKey(SelectedItem)); });
        }

        private async Task RunMutationOperation(Func<Task> operation)
        {
            await ExecuteBusyOperationAsync(operation);
        }

        private void UpdateStatus()
        {
            var explicitCount = PlanItems.Count(delegate(PlanningItem item) { return item.IsExplicitPlan; });
            var calculatedCount = PlanItems.Count - explicitCount;
            var nonPersistableCount = PlanItems.Count(delegate(PlanningItem item) { return !item.CanPersistPlan; });

            StatusText = string.Format(
                "Год: {0}. Позиций: {1}. Явных планов EKZMCP: {2}. Расчетных позиций: {3}. Без TPRMCP: {4}.",
                SelectedYear,
                PlanItems.Count,
                explicitCount,
                calculatedCount,
                nonPersistableCount);
        }
    }
}
