using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

using C4IT.FASD.Base;
using C4IT.FASD.Cockpit.Communication;
using C4IT.MultiLanguage;
using FasdDesktopUi.Basics.Services.RelationService;
using FasdDesktopUi.Basics.Services.SupportCaseSearchService;
using static C4IT.Logging.cLogManager;



namespace FasdDesktopUi.Basics
{
    public interface ISearchUiProvider
    {
        SupportCaseSearchService SearchService { get; }
        void SetSearchHistoryVisibility(bool isVisible);
        void ShowLoadingTextItem(string itemText);
        void ShowSearchRelations(cSearchHistorySearchResultEntry SearchHistoryEntry, IRelationService relationService, ISearchUiProvider SearchUiProvider);
        void SetPendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses);
        void UpdatePendingInformationClasses(HashSet<enumFasdInformationClass> informationClasses);
    }

    public class cSearchManager
    {
        public const int constMaxHistoryEntryCount = 5;      // max. number of entries in the search result history
        public const int RefreshPeriod = 60;                // time in seconds until the data should be refreshed

        private static cSearchManager _Instance = null;
        public static cSearchManager Instance
        {
            get
            {
                return (_Instance is null) ? _Instance = new cSearchManager() : _Instance;
            }
        }

        public List<cSearchHistoryEntryBase> HistoryList { get; private set; } = new List<cSearchHistoryEntryBase>();

        private cSearchManager()
        {
        }

        public void AddEntry(cSearchHistoryEntryBase Entry)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                // restict the max. history length
                while (HistoryList.Count >= constMaxHistoryEntryCount)
                    HistoryList.RemoveAt(0);

                // check if we have already the same entry in the list
                cSearchHistoryEntryBase alreadyExists = null;
                foreach (var _entry in HistoryList)
                    if (_entry.DisplayText == Entry.DisplayText)
                    {
                        alreadyExists = _entry;
                        break;
                    }
                // and remove the duplacated entry
                if (alreadyExists != null)
                    HistoryList.Remove(alreadyExists);

                // and the new entry at least
                HistoryList.Add(Entry);
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        public void ReplaceEntry(cSearchHistoryEntryBase Entry, cSearchHistoryEntryBase EntryToReplace)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                HistoryList.Remove(EntryToReplace);
                AddEntry(Entry);
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        private class cUserInfo
        {
            internal string account;
            internal string domain;
            internal string type;

            public override bool Equals(object obj)
            {
                if (this is null && obj is null)
                    return true;

                if (this is null || obj is null)
                    return false;

                if (!(obj is cUserInfo userInfo))
                    return false;

                return (type == userInfo.type && account == userInfo.account && domain == userInfo.domain);
            }

            public override int GetHashCode()
            {
                return base.GetHashCode();
            }
        }

        private static cUserInfo GetUserInfo(cF4sdApiSearchResultRelation searchRelation)
        {
            try
            {
                if (searchRelation?.Infos is null)
                    return null;

                searchRelation.Infos.TryGetValue("UserAccountType", out var _accountType);
                searchRelation.Infos.TryGetValue("UserAccount", out var _account);
                searchRelation.Infos.TryGetValue("UserDomain", out var _domain);
                switch (searchRelation.Type)
                {
                    case enumF4sdSearchResultClass.User:
                        if (string.IsNullOrEmpty(_account))
                            searchRelation.Infos.TryGetValue("account", out _account);
                        if (string.IsNullOrEmpty(_domain))
                            searchRelation.Infos.TryGetValue("domain", out _domain);
                        break;
                    case enumF4sdSearchResultClass.Computer:
                    case enumF4sdSearchResultClass.MobileDevice:
                    case enumF4sdSearchResultClass.VirtualSession:
                        if (string.IsNullOrEmpty(_account))
                            searchRelation.Infos.TryGetValue("relation_account", out _account);
                        if (string.IsNullOrEmpty(_domain))
                            searchRelation.Infos.TryGetValue("relation_domain", out _domain);
                        break;
                    default:
                        return null;
                }
                return new cUserInfo()
                {
                    account = _account,
                    domain = _domain,
                    type = _accountType
                };
            }
            catch (Exception E)
            {
                LogException(E);
            }
            return null;
        }

        /// <summary>
        /// Add identifier to relation name, if there are relations with different user infos.
        /// </summary>
        /// <param name="relations"></param>
        internal static void ResolveRelations(List<cF4sdApiSearchResultRelation> relations)
        {
            try
            {
                cUserInfo firstUserInfo = null;
                var resolveAccount = false;
                foreach (var relation in relations)
                {
                    relation.DisplayName = relation.Name;

                    if (relation.Type is enumF4sdSearchResultClass.Ticket)
                        continue;

                    var userInfo = GetUserInfo(relation);
                    if (firstUserInfo is null)
                        firstUserInfo = userInfo;

                    resolveAccount = resolveAccount || !firstUserInfo.Equals(userInfo);
                }

                if (!resolveAccount)
                    return;

                foreach (var relation in relations)
                {
                    if (relation.Type is enumF4sdSearchResultClass.Ticket || relation.Type is enumF4sdSearchResultClass.VirtualSession || relation.Type is enumF4sdSearchResultClass.MobileDevice)
                        continue;

                    string relationName = relation.Name;

                    var userInfo = GetUserInfo(relation);
                    switch (userInfo.type)
                    {
                        case "Local":
                            relationName = cMultiLanguageSupport.GetItem("UserSerach.AdvancedInfo.LocalUser");
                            break;
                        case "Azure":
                            relationName = cMultiLanguageSupport.GetItem("UserSerach.AdvancedInfo.AzureUser");
                            break;
                        default:
                            relationName = cMultiLanguageSupport.GetItem("UserSerach.AdvancedInfo.AdUser");
                            break;

                    }
                    relationName = string.Format(relationName, relation.DisplayName, userInfo.account, userInfo.domain);
                    relation.DisplayName = relationName;
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        public static async Task<List<cF4sdApiSearchResultRelation>> GetCaseRelationsAsync(List<cFasdApiSearchResultEntry> SearchResults, cF4sdApiSearchResultRelation CaseRelation = null)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);
            try
            {
                var firstSearchResultData = SearchResults.First();

                // get SearchResult relation data
                var CaseRelations = await cFasdCockpitCommunicationBase.Instance.GetSearchResultRelations(firstSearchResultData.Type, SearchResults);

                // add the passed CaseRelation if it not within the result
                var addRelation = CaseRelation != null;
                if (addRelation)
                    foreach (var _caseRelation in CaseRelations)
                    {
                        if (_caseRelation.Equals(CaseRelation))
                        {
                            addRelation = false;
                            break;
                        }
                    }

                if (addRelation)
                    CaseRelations.Add(CaseRelation);

                // if we have no relation, create a 'dummy' relation to relate to the search result itself
                if (CaseRelations?.Count == 0)
                {
                    var _dummyRelation = new cF4sdApiSearchResultRelation(firstSearchResultData);
                    CaseRelations.Add(_dummyRelation);
                }

                if (CaseRelations?.Count > 0)
                    CaseRelations = CaseRelations.OrderByDescending(relation => relation.Type)
                        .ThenBy(relation => relation.LastUsed)
                        .ThenBy(relation => relation.UsingLevel)
                        .ToList();

                return CaseRelations;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                LogMethodEnd(CM);
            }

            return null;
        }
    }

    public abstract class cSearchHistoryEntryBase
    {
        public string DisplayText { get; private set; }
        public bool isSeen { get; set; } = false;

        protected DateTime LastRefresh = DateTime.MinValue;
        public List<cFasdApiSearchResultEntry> SelectedSearchResult { get; private set; }
        public List<cF4sdApiSearchResultRelation> Relations { get; protected set; }

        public ISearchUiProvider SearchUiProvider { get; private set; }

        public cSearchHistoryEntryBase(string DisplayText, List<cFasdApiSearchResultEntry> selectedSearchResult, List<cF4sdApiSearchResultRelation> relations, ISearchUiProvider SearchUiProvider)
        {
            this.DisplayText = DisplayText;
            this.isSeen = isSeen;
            this.SelectedSearchResult = selectedSearchResult;
            this.Relations = relations;
            this.SearchUiProvider = SearchUiProvider;
            LastRefresh = DateTime.UtcNow;
        }

        public abstract Task RefreshAsync();

        public bool ShouldRefresh()
        {
            return (DateTime.UtcNow - LastRefresh).TotalSeconds > cSearchManager.RefreshPeriod;
        }
    }

    public class cSearchHistorySearchResultEntry : cSearchHistoryEntryBase
    {
        public string HeaderText { get; private set; }

        public cSearchHistorySearchResultEntry(string DisplayText, string HeaderText, List<cFasdApiSearchResultEntry> selectedSearchResult, List<cF4sdApiSearchResultRelation> relations, ISearchUiProvider SearchUiProvider) : base(DisplayText, selectedSearchResult, relations, SearchUiProvider)
        {
            this.HeaderText = HeaderText;
            LastRefresh = DateTime.UtcNow;
        }

        public async override Task RefreshAsync()
        {
            if (ShouldRefresh())
            {
                var RetVal = await cSearchManager.GetCaseRelationsAsync(SelectedSearchResult);
                if (RetVal != null)
                {
                    Relations = RetVal;
                    LastRefresh = DateTime.UtcNow;
                }
            }
        }
    }

    public class cSearchHistoryRelationEntry : cSearchHistoryEntryBase
    {
        public cF4sdApiSearchResultRelation SelectedRelation { get; private set; }

        public cSearchHistoryRelationEntry(string DisplayText, List<cFasdApiSearchResultEntry> selectedSearchResult, List<cF4sdApiSearchResultRelation> relations, cF4sdApiSearchResultRelation selectedRealtion, ISearchUiProvider SearchUiProvider) : base(DisplayText, selectedSearchResult, relations, SearchUiProvider)
        {
            SelectedRelation = selectedRealtion;
        }

        public async override Task RefreshAsync()
        {
            if (ShouldRefresh())
            {
                var RetVal = await cSearchManager.GetCaseRelationsAsync(SelectedSearchResult, SelectedRelation);
                if (RetVal != null)
                {
                    Relations = RetVal;
                    LastRefresh = DateTime.UtcNow;
                }
            }
        }
    }

}
