using C4IT.FASD.Base;
using C4IT.Logging;
using C4IT.MultiLanguage;
using FasdDesktopUi.Basics.Enums;
using FasdDesktopUi.Basics.Services.RelationService;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using static C4IT.Logging.cLogManager;

namespace FasdDesktopUi.Basics.UiActions
{
    public class cUiProcessSearchResultAction : cUiActionBase
    {
        private readonly ISearchUiProvider _searchUiProvider;
        private readonly List<cFasdApiSearchResultEntry> _searchResults;

        private readonly List<cF4sdApiSearchResultRelation> _loadedRelations = new List<cF4sdApiSearchResultRelation>();
        private readonly TaskCompletionSource<bool> _isSearchUnambigous = new TaskCompletionSource<bool>();

        private IRelationService _relationService = null;

        public cF4sdApiSearchResultRelation PreSelectedSearchRelation { get; set; } = null;

        public cUiProcessSearchResultAction(string name, ISearchUiProvider searchUiProvider, List<cFasdApiSearchResultEntry> searchResults)
        {
            Name = name;
            _searchUiProvider = searchUiProvider;
            _searchResults = searchResults;

            _searchUiProvider.SearchService.RelationsFound += HandleRelationsFound;
        }

        ~cUiProcessSearchResultAction()
        {
            _searchUiProvider.SearchService.RelationsFound -= HandleRelationsFound;
        }

        /// <returns>If the search is unfinished.
        /// False: search is finished.
        /// True: search is ongoing.</returns>
        public override async Task<bool> RunUiActionAsync(object sender, UIElement uiLocation, bool isDetailedLayout, cSupportCaseDataProvider dataProvider)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                if (dataProvider != null)
                {
                    Debug.Assert(true, "At this moment, we want to create a new support call, so we must not have a dataProvider");
                    LogEntry("At this moment, we want to create a new support call, so we must not have a dataProvider", LogLevels.Error);
                    return false;
                }

                return await ProcessSearchResultDefaultAsync(CancellationToken.None);
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return false;
        }

        /// <returns>If the search is unfinished. 
        /// False: search is finished.
        /// True: search is ongoing.</returns>
        private async Task<bool> ProcessSearchResultDefaultAsync(CancellationToken token)
        {
            try
            {
                if (_searchResults is null || _searchResults.Count <= 0)
                    return false;
                _searchUiProvider.ShowLoadingTextItem(cMultiLanguageSupport.GetItem("Searchbar.Loading.Relations"));

                cF4sdStagedSearchResultRelationTaskId gatherRelationTask = await _searchUiProvider.SearchService.LoadRelationsAsync(_searchResults, token);

                HashSet<enumFasdInformationClass> orderedPendingInfoClasses
                    = GetInformationClassOrderedByPriority(gatherRelationTask.PendingInformationClasses, cF4sdIdentityEntry.GetFromSearchResult(_searchResults.FirstOrDefault()?.Type ?? enumF4sdSearchResultClass.Unknown));
                _searchUiProvider.SetPendingInformationClasses(orderedPendingInfoClasses);

                Mouse.OverrideCursor = null;

                if (await _isSearchUnambigous.Task)
                    return await ProcessSearchResultRelationAsync(_searchResults.First().Name, _loadedRelations, _loadedRelations.FirstOrDefault());

                if (MatchPreselectedSearchRelation(_loadedRelations))
                    return await ProcessSearchResultRelationAsync(_searchResults.First().Name, _loadedRelations, PreSelectedSearchRelation);

                return true;
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            return false;

            bool MatchPreselectedSearchRelation(IEnumerable<cF4sdApiSearchResultRelation> relations)
            {
                if (PreSelectedSearchRelation is null || relations is null)
                    return false;
                return relations.Any(entry => entry.Type == PreSelectedSearchRelation.Type && entry.id == PreSelectedSearchRelation.id);
            }
        }

        private async Task<bool> ProcessSearchResultRelationAsync(string name, List<cF4sdApiSearchResultRelation> caseRelations, cF4sdApiSearchResultRelation selectedRelation)
        {
            var relationSearchResult = new cSearchHistorySearchResultEntry(_searchResults.FirstOrDefault().DisplayName, _searchResults.FirstOrDefault().DisplayName, _searchResults, caseRelations, _searchUiProvider);
            string displayName = !string.IsNullOrWhiteSpace(selectedRelation?.Name) ? $"{name} → {selectedRelation.Name}" : name;

            cUiProcessSearchRelationAction action
                = new cUiProcessSearchRelationAction(displayName, _searchResults, caseRelations, selectedRelation, _searchUiProvider, relationSearchResult, _relationService)
                {
                    DisplayType = enumActionDisplayType.enabled,
                };

            return await action.RunUiActionAsync(this, null, false, null);
        }

        private HashSet<enumFasdInformationClass> GetInformationClassOrderedByPriority(HashSet<enumFasdInformationClass> informationClasses, enumFasdInformationClass? excludedInformationClass = null)
        {
            try
            {
                var informationClassPrio = cFasdCockpitConfig.Instance.Global.InformationClassSearchPriority;

                if (excludedInformationClass.HasValue)
                    informationClasses.Remove(excludedInformationClass.Value);

                if (informationClassPrio is null || informationClassPrio.Count == 0)
                    return informationClasses;

                return informationClasses
                    .OrderBy(infoClass => informationClassPrio.Contains(infoClass) ? informationClassPrio.IndexOf(infoClass) : int.MaxValue)
                    .ToHashSet();
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            return informationClasses;
        }

        private void HandleRelationsFound(object sender, StagedSearchResultRelationsEventArgs e)
        {
            try
            {
                _loadedRelations.AddRange(e.StagedResultRelations.Relations);
                _relationService = e.RelationService;

                if (!e.StagedResultRelations.IsComplete)
                    return;

                bool isSearchResultUnambigous = _loadedRelations.Count <= 1;

                if (_loadedRelations.Count == 0)
                    _loadedRelations.Add(new cF4sdApiSearchResultRelation(_searchResults.FirstOrDefault()));

                _isSearchUnambigous.TrySetResult(isSearchResultUnambigous);
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }
    }

}
