using C4IT.FASD.Base;
using C4IT.FASD.Cockpit.Communication;
using C4IT.Logging;
using FasdDesktopUi.Basics.CustomEvents;
using FasdDesktopUi.Basics.Models;
using FasdDesktopUi.Pages.DetailsPage.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static C4IT.Logging.cLogManager;

namespace FasdDesktopUi.Basics.Services.SupportCase
{
    /// <summary>
    /// Used for processing raw data of a <see cref="ISupportCase"/> for the UI in a certain way.
    /// </summary>
    internal class SupportCaseProcessor
    {
        private ISupportCase _supportCase;
        public cSupportCaseDataProvider SupportCaseDataProviderArtifact { get => _supportCase?.SupportCaseDataProviderArtifact; }

        private readonly Dictionary<cF4sdApiSearchResultRelation, cDetailsPageData> _detailsPageDataCache = new Dictionary<cF4sdApiSearchResultRelation, cDetailsPageData>();

        private readonly Dictionary<cF4sdApiSearchResultRelation, Dictionary<string, object>> _namedParameterCache = new Dictionary<cF4sdApiSearchResultRelation, Dictionary<string, object>>();

        internal void SetSupportCase(ISupportCase supportCase)
        {
            if (_supportCase != null)
            {
                _supportCase.CaseRelationsAdded -= HandleSupportCaseRelationsAdded;
                _supportCase.SupportCaseDataCacheHasChanged -= HandleSupportCaseDataCacheHasChanged;
            }

            _supportCase = supportCase;

            _supportCase.CaseRelationsAdded += HandleSupportCaseRelationsAdded;
            _supportCase.SupportCaseDataCacheHasChanged += HandleSupportCaseDataCacheHasChanged;
        }

        private void HandleSupportCaseRelationsAdded(object sender, RelationEventArgs e)
            => AvailableCaseRelationsAdded?.Invoke(this, e);

        private async void HandleSupportCaseDataCacheHasChanged(object sender, SupportCaseDataEventArgs e)
        {
            bool isArtifactShowingCorrectHealthCard
                = SupportCaseDataProviderArtifact.HealthCardDataHelper.SelectedHealthCard == GetHealthcardFor(e.Relation);

            if (!isArtifactShowingCorrectHealthCard)
            {
                // todo this can probably be removed, as soon as the last dependency of the SupportCaseDataProviderArtifact is gone.
                // till then the detailspageData gets overriden with the detailspageData of the new relation.
                // However, the removal shouldn't be much of a problem, due to the fact the Artifact also stores the raw data
                _detailsPageDataCache.Remove(e.Relation);
                return;
            }

            if (_detailsPageDataCache.TryGetValue(e.Relation, out var cachedData))
            {
                cDetailsPageData detailData = _supportCase.SupportCaseDataProviderArtifact.HealthCardDataHelper.DetailPage.GetDataWithoutHeading();
                cachedData.WidgetData = detailData.WidgetData;
                cachedData.DataHistoryList = detailData.DataHistoryList;
                cachedData.MenuBarData = detailData.MenuBarData;
                cachedData.DataContainerCollectionList = detailData.DataContainerCollectionList;
            }
            else
            {
                _detailsPageDataCache[e.Relation] = await _supportCase.SupportCaseDataProviderArtifact.HealthCardDataHelper.DetailPage.GetDataAsync();
            }

            UpdateNamedParameters(e.Relation, e.DataTables);
            CaseDataChanged?.Invoke(this, e);
        }

        private void UpdateNamedParameters(cF4sdApiSearchResultRelation relation, IEnumerable<cF4SDHealthCardRawData.cHealthCardTable> dataTables)
        {
            try
            {
                if (!_namedParameterCache.ContainsKey(relation))
                    _namedParameterCache.Add(relation, new Dictionary<string, object>());

                var healthcard = GetHealthcardFor(relation);

                foreach (var namedParameter in cHealthCardPrerequisites.GetNamedParameters(healthcard).Values)
                {
                    var table = dataTables.FirstOrDefault(t => t.Name == namedParameter.DatabaseInfo.ValueTable);

                    if (table is null)
                        continue;

                    if (!table.Columns.TryGetValue(namedParameter.DatabaseInfo.ValueColumn, out var column))
                        continue;

                    string value = cUtility.RawValueFormatter.GetDisplayValue(column.Values.FirstOrDefault(), namedParameter.Display);
                    _namedParameterCache[relation][namedParameter.ParameterName] = value;
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        public async Task LoadSupportCaseDataAsync(cF4sdApiSearchResultRelation relation, IEnumerable<string> tablesToLoad)
        {
            _ = Task.Run(async () => await _supportCase.LoadSupportCaseDataAsync(relation, tablesToLoad.Where(t => !t.Contains("-details-"))));

            if (!_detailsPageDataCache.TryGetValue(relation, out var detailsData))
            {
                detailsData = await _supportCase.SupportCaseDataProviderArtifact.HealthCardDataHelper.DetailPage.GetDataAsync();
                _detailsPageDataCache.Add(relation, detailsData);
            }
            else
            {
                _supportCase.SupportCaseDataProviderArtifact.HealthCardDataHelper.UpdateNamedParameterEntries();
            }

            CaseDataChanged?.Invoke(this, new SupportCaseDataEventArgs());
        }

        public async Task UpdateLatestCaseDataFor(cF4sdApiSearchResultRelation relation)
        {
            try
            {
                int? agentUserId = relation.Identities.FirstOrDefault(i => i.Class == enumFasdInformationClass.User)?.agentId;
                int? agentDeviceId = relation.Identities.FirstOrDefault(i => i.Class == enumFasdInformationClass.Computer)?.agentId;

                await ActualizeDataAsync(agentUserId, agentDeviceId);
                _supportCase.InvalidateLatestCaseDataCacheFor(relation, out var invalidatedTables);
                _detailsPageDataCache.Remove(relation);
                await _supportCase.LoadSupportCaseDataAsync(relation, invalidatedTables.Where(t => !t.Name.StartsWith("Computation_")).Select(t => t.Name));
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        private async Task<enumActualizeStatus> ActualizeDataAsync(int? agentUserId, int? agentDeviceId)
        {
            var status = enumActualizeStatus.unknown;

            if (!agentUserId.HasValue && !agentDeviceId.HasValue)
                return status;

            try
            {
                TimeSpan refreshDelay = TimeSpan.FromMilliseconds(500);
                const int maxPollCount = 20;

                if (!agentDeviceId.HasValue)
                {
                    LogEntry("Coudldn't acutalize data. There was no valid AgentDeviceId found.", LogLevels.Error);
                    return status;
                }

                var taskId = await cFasdCockpitCommunicationBase.Instance.ActualizeAgentData(agentDeviceId.Value, agentUserId);

                if (taskId == Guid.Empty)
                    return enumActualizeStatus.failed;

                enumFasdInformationClass informationClass = agentUserId != null ? enumFasdInformationClass.User : enumFasdInformationClass.Computer;
                int pollCount = 0;

                do
                {
                    status = await cFasdCockpitCommunicationBase.Instance.GetActualizeAgentDataStatus(taskId, informationClass);

                    if (status == enumActualizeStatus.unknown)
                    {
                        pollCount++;
                        if (pollCount >= maxPollCount)
                            return status;

                        await Task.Delay(refreshDelay);
                    }
                } while (status == enumActualizeStatus.unknown);
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return status;
        }

        internal ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> GetCaseRelations()
            => _supportCase.GetCaseRelations();

        internal cHealthCard GetHealthcardFor(cF4sdApiSearchResultRelation relation)
        {
            var availableHealthCards = cF4SDCockpitXmlConfig.Instance?.HealthCardConfig?.HealthCards?.Values;

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

            return availableHealthCards
                .FirstOrDefault(hc =>
                    hc.InformationClasses.All(i => i == cF4sdIdentityEntry.GetFromSearchResult(relation.Type))
                    && HasCockpitUserRequiredRoles(hc.RequiredRoles));
        }

        private static bool HasCockpitUserRequiredRoles(List<string> requiredRoles)
        {
            try
            {
                if (requiredRoles is null || requiredRoles.Count == 0)
                    return true;

                List<string> roles = null;
                lock (cFasdCockpitCommunicationBase.CockpitUserInfoLock)
                {
                    roles = cFasdCockpitCommunicationBase.CockpitUserInfo?.Roles;
                }
                if (roles is null || roles.Count == 0)
                    return false;

                foreach (var requiredRole in requiredRoles)
                {
                    if (roles.Contains(requiredRole, StringComparer.InvariantCultureIgnoreCase))
                        return true;
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
            return false;
        }

        public List<List<cWidgetValueModel>> GetWidgetData(cF4sdApiSearchResultRelation relation)
        {
            List<List<cWidgetValueModel>> widgetData = null;

            if (_detailsPageDataCache.TryGetValue(relation, out var detailsData))
                widgetData = detailsData?.WidgetData;

            return widgetData ?? new List<List<cWidgetValueModel>>();
        }

        public cDetailsPageDataHistoryDataModel GetHistoryData(cF4sdApiSearchResultRelation relation)
        {
            cDetailsPageDataHistoryDataModel historyData = null;

            if (_detailsPageDataCache.TryGetValue(relation, out var detailsData))
                historyData = detailsData?.DataHistoryList;

            return historyData ?? new cDetailsPageDataHistoryDataModel();
        }

        public List<cContainerCollectionData> GetContainerData(cF4sdApiSearchResultRelation relation)
        {
            List<cContainerCollectionData> containerData = null;

            if (_detailsPageDataCache.TryGetValue(relation, out var detailsData))
                containerData = detailsData?.DataContainerCollectionList;

            return containerData ?? new List<cContainerCollectionData>();
        }

        public List<cMenuDataBase> GetMenuBarData(cF4sdApiSearchResultRelation relation)
        {
            List<cMenuDataBase> menuData = null;

            if (_detailsPageDataCache.TryGetValue(relation, out var detailsData))
                menuData = detailsData?.MenuBarData;

            return menuData ?? new List<cMenuDataBase>();
        }

        /// <summary>
        /// Raised when newly available relations for a support case were added.
        /// </summary>
        public event EventHandler<RelationEventArgs> AvailableCaseRelationsAdded;

        /// <summary>
        /// Raised when the data set of a support case has changed.
        /// </summary>
        public event EventHandler<SupportCaseDataEventArgs> CaseDataChanged;
    }
}
