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

namespace FasdDesktopUi.Basics.Services.SupportCase
{
    public class SupportCase : ISupportCase
    {
        private readonly Dictionary<enumFasdInformationClass, IList<cF4sdApiSearchResultRelation>> _caseRelations = new Dictionary<enumFasdInformationClass, IList<cF4sdApiSearchResultRelation>>();
        private readonly Dictionary<string, Dictionary<cF4sdApiSearchResultRelation, cF4SDHealthCardRawData.cHealthCardTable>> _supportCaseDataCache = new Dictionary<string, Dictionary<cF4sdApiSearchResultRelation, cF4SDHealthCardRawData.cHealthCardTable>>();
        private readonly Dictionary<cF4sdApiSearchResultRelation, cF4SDHealthCardRawData> _rawDataCache = new Dictionary<cF4sdApiSearchResultRelation, cF4SDHealthCardRawData>(); // todo remove, currently only used for SupportCaseDataProviderArtifact

        private readonly ICollection<cF4sdApiSearchResultRelation> _relationsCurrentlyLoadingData = new HashSet<cF4sdApiSearchResultRelation>();

        internal readonly Guid Id;
        private readonly IRelationService _relationService;
        public cSupportCaseDataProvider SupportCaseDataProviderArtifact { get; }

        internal SupportCase(Guid id, IRelationService relationService, cSupportCaseDataProvider supportCaseDataProvider)
        {
            Id = id;
            _relationService = relationService;
            SupportCaseDataProviderArtifact = supportCaseDataProvider;

            AddCaseRelations(_relationService?.GetLoadedRelations());
        }

        ~SupportCase()
        {
            if (_relationService != null)
                _relationService.RelationsFound -= HandleRelationsFound;
        }

        public void Initialize()
        {
            if (_relationService != null)
                _relationService.RelationsFound += HandleRelationsFound;
        }

        public ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> GetCaseRelations()
        {
            try
            {
                IEnumerable<(enumFasdInformationClass InformationClass, cF4sdApiSearchResultRelation Relation)> flatList
                    = _caseRelations.SelectMany(i => i.Value.Select(v => (i.Key, v)));

                return flatList.ToLookup(v => v.InformationClass, v => v.Relation);
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            return null;
        }

        public void AddCaseRelations(ILookup<enumFasdInformationClass, cF4sdApiSearchResultRelation> relations)
        {
            try
            {
                if (relations is null)
                    return;

                foreach (var relationType in relations)
                {
                    if (_caseRelations.TryGetValue(relationType.Key, out var caseRelation))
                        caseRelation = caseRelation.Union(relationType).ToList();
                    else
                        _caseRelations.Add(relationType.Key, relationType.ToList());

                    if (SupportCaseDataProviderArtifact?.CaseRelations?.TryGetValue(relationType.Key, out var caseRelations) ?? false)
                        caseRelations = caseRelations.Union(relationType).ToList();
                    else
                        SupportCaseDataProviderArtifact?.CaseRelations?.Add(relationType.Key, relationType.ToList());
                }

                CaseRelationsAdded?.Invoke(this, new RelationEventArgs() { Relations = relations });
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        public async Task LoadSupportCaseDataAsync(cF4sdApiSearchResultRelation relation, IEnumerable<string> tablesToLoad)
        {
            try
            {
                if (_relationsCurrentlyLoadingData.Contains(relation))
                    return;

                _relationsCurrentlyLoadingData.Add(relation);

                cF4SDHealthCardRawData rawData = null;

                // todo this is only a temporary fix. Currently the tablesToLoad contain also detail tables
                // and tables won't be loaded e.g. the QuickActionHistory
                bool isDataComplete = IsRawDataCacheComplete() && IsSupportCaseDataCacheComplete();

                if (isDataComplete)
                {
                    await SupportCaseDataProviderArtifact.HealthCardDataHelper.LoadingHelper.SetHealthCardRawData(_rawDataCache[relation], relation.Identities);
                    return;
                }

                var rawDataRequest = new cF4sdHealthCardRawDataRequest()
                {
                    Identities = relation.Identities,
                    Tables = tablesToLoad.ToList(),
                    MaxAge = cF4SDCockpitXmlConfig.Instance?.HealthCardConfig?.SearchResultAge ?? 14
                };

                while (!isDataComplete)
                {
                    if (rawData is null)
                    {
                        rawData = await cFasdCockpitCommunicationBase.Instance.GetHealthCardData(rawDataRequest);
                        _rawDataCache[relation] = rawData;
                        await SupportCaseDataProviderArtifact.HealthCardDataHelper.LoadingHelper.SetHealthCardRawData(rawData, rawDataRequest.Identities);
                    }
                    else
                    {
                        rawData = await cFasdCockpitCommunicationBase.Instance.GetHealthCardData(rawData.Id);
                        _rawDataCache[relation] = _rawDataCache[relation].Combine(rawData);
                        await SupportCaseDataProviderArtifact.HealthCardDataHelper.LoadingHelper.UpdateHealthcardRawData(rawData);
                    }

                    UpdateSupportCaseDataCache(relation, rawData?.Tables?.Values);

                    isDataComplete = rawData?.Tables?
                        .Where(table => table.Key.StartsWith("Computation_") == false)
                        .All(table => !table.Value.IsIncomplete && !table.Value.Columns.Values.Any(c => c.IsIncomplete)) ?? false;
                }

                SupportCaseDataProviderArtifact.HealthCardDataHelper.LoadingHelper.LastDataRequest = DateTime.Now;
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
            finally
            {
                _relationsCurrentlyLoadingData.Remove(relation);
            }

            bool IsRawDataCacheComplete()
            {
                return tablesToLoad.All(t =>
                    _rawDataCache.TryGetValue(relation, out var cachedRawData)
                    && (cachedRawData?.Tables?.TryGetValue(t, out var cachedTable) ?? false)
                    && cachedTable != null && !cachedTable.IsIncomplete && !cachedTable.Columns.Values.Any(c => c.IsIncomplete)
                );
            }

            bool IsSupportCaseDataCacheComplete()
            {
                return tablesToLoad.Any(t =>
                    _supportCaseDataCache.TryGetValue(t, out var cachedTables)
                    && cachedTables.TryGetValue(relation, out var table)
                    && !table.IsIncomplete && !table.Columns.Values.Any(c => c.IsIncomplete)
                );
            }
        }

        public void UpdateSupportCaseDataCache(cF4sdApiSearchResultRelation relation, IEnumerable<cF4SDHealthCardRawData.cHealthCardTable> tables)
        {
            try
            {
                if (tables is null)
                    return;

                List<cF4SDHealthCardRawData.cHealthCardTable> dataTables = tables?.ToList();

                foreach (var table in dataTables)
                {
                    if (string.IsNullOrEmpty(table.Name))
                        continue;

                    if (!_supportCaseDataCache.ContainsKey(table.Name))
                        _supportCaseDataCache.Add(table.Name, new Dictionary<cF4sdApiSearchResultRelation, cF4SDHealthCardRawData.cHealthCardTable>());

                    if (!_supportCaseDataCache[table.Name].ContainsKey(relation))
                        _supportCaseDataCache[table.Name][relation] = table;
                    else if (_supportCaseDataCache[table.Name][relation].IsIncomplete)
                        MergeTable(_supportCaseDataCache[table.Name][relation], table);
                }

                SupportCaseDataCacheHasChanged?.Invoke(this, new SupportCaseDataEventArgs() { Relation = relation, DataTables = dataTables });
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            void MergeTable(cF4SDHealthCardRawData.cHealthCardTable existingTable, cF4SDHealthCardRawData.cHealthCardTable newTable)
            {
                try
                {
                    existingTable.IsIncomplete = newTable.IsIncomplete;

                    foreach (var newColumn in newTable.Columns)
                    {
                        existingTable.Columns[newColumn.Key] = newColumn.Value;
                    }
                }
                catch (Exception ex)
                {
                    LogException(ex);
                }
            }
        }

        public void InvalidateCaseDataCacheFor(cF4sdApiSearchResultRelation relation)
        {
            try
            {
                _rawDataCache.Remove(relation);

                foreach (var tableCache in _supportCaseDataCache.Values)
                {
                    tableCache.Remove(relation);
                }

                // todo: invoke SupportCaseDataChache with empty tables
                // SupportCaseDataCacheHasChanged?.Invoke(this, new SupportCaseDataEventArgs() { Relation = relation });
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        public void InvalidateLatestCaseDataCacheFor(cF4sdApiSearchResultRelation relation, out ICollection<cF4SDHealthCardRawData.cHealthCardTable> invalidatedTables)
        {
            invalidatedTables = new HashSet<cF4SDHealthCardRawData.cHealthCardTable>();
            try
            {
                foreach (var tableCache in _supportCaseDataCache.Values)
                {
                    if (!tableCache.TryGetValue(relation, out cF4SDHealthCardRawData.cHealthCardTable table))
                        continue;

                    table.IsIncomplete = true;

                    foreach (var column in table.Columns.Values)
                    {
                        if (column.Values != null && column.Values.Count > 0)
                            column.Values[0] = null;
                        column.IsIncomplete = true;
                    }

                    invalidatedTables.Add(table);
                }

                if (!_rawDataCache.TryGetValue(relation, out var cachedRawData) || cachedRawData?.Tables is null)
                    return;

                foreach (var table in cachedRawData.Tables.Values)
                {
                    table.IsIncomplete = true;

                    foreach (var column in table.Columns.Values)
                    {
                        if (column.Values != null && column.Values.Count > 0)
                            column.Values[0] = null;
                        column.IsIncomplete = true;
                    }
                }
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
        }

        public IEnumerable<object> GetSupportCaseHealthcardData(cF4sdApiSearchResultRelation relation, cValueAddress valueAddress)
        {
            try
            {
                if (!_supportCaseDataCache.TryGetValue(valueAddress.ValueTable, out var tables))
                    return null;

                if (!tables.TryGetValue(relation, out var table))
                    return null;

                if (!table.Columns.TryGetValue(valueAddress.ValueColumn, out var column))
                    return null;

                return column.Values;
            }
            catch (Exception ex)
            {
                LogException(ex);
            }

            return null;
        }

        private void HandleRelationsFound(object sender, StagedSearchResultRelationsEventArgs e)
        {
            AddCaseRelations(e.StagedResultRelations.Relations);

            if (e.StagedResultRelations.IsComplete)
                _relationService.RelationsFound -= HandleRelationsFound;
        }

        private void AddCaseRelations(IEnumerable<cF4sdApiSearchResultRelation> relations)
            => AddCaseRelations(relations?.ToLookup(r => cF4sdIdentityEntry.GetFromSearchResult(r.Type), r => r));

        public event EventHandler<RelationEventArgs> CaseRelationsAdded;
        public event EventHandler<SupportCaseDataEventArgs> SupportCaseDataCacheHasChanged;
    }
}
