using C4IT.F4SD.DisplayFormatting;
using C4IT.FASD.Base;
using C4IT.FASD.Cockpit.Communication;
using C4IT.Logging;
using C4IT.MultiLanguage;
using FasdDesktopUi.Basics.Models;
using FasdDesktopUi.Basics.UserControls;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

using static C4IT.FASD.Base.cF4SDHealthCardRawData;
using static C4IT.Logging.cLogManager;

namespace FasdDesktopUi.Basics.UiActions
{
    public class cShowDetailedDataAction : cUiActionBase
    {
        private static readonly Dictionary<cF4sdIdentityList, Dictionary<string, cF4SDHealthCardRawData.cHealthCardDetailsTable>> CachedDetails = new Dictionary<cF4sdIdentityList, Dictionary<string, cF4SDHealthCardRawData.cHealthCardDetailsTable>>();

        private cSupportCaseDataProvider DataProvider;

        private readonly cHealthCardStateBase StateDefinition;

        private int DayIndex { get; set; }

        private string DetailsTitle { get; set; }

        private string Recommendation { get; set; }

        private readonly cHealthCardDetailsTable _valuedDetailsData = null;

        public cShowDetailedDataAction(cHealthCardStateBase stateDefinition, int dayIndex = 0, cHealthCardDetailsTable valuedDetailsData = null)
        {
            StateDefinition = stateDefinition;
            DayIndex = dayIndex;
            DetailsTitle = stateDefinition.Names.GetValue();
            Recommendation = stateDefinition.Descriptions.GetValue();
            if (Recommendation == string.Empty)
                Recommendation = null;
            _valuedDetailsData = valuedDetailsData;
        }

        public override async Task<bool> RunUiActionAsync(object sender, UIElement UiLocation, bool isDetailedLayout, cSupportCaseDataProvider dataProvider)
        {
            try
            {
                if (!(UiLocation is Decorator decorator))
                    return false;

                DataProvider = dataProvider;
                DataProvider.HealthCardDataHelper.LoadingHelper.DataRefreshed += (s, e) => CachedDetails.Clear();

                Mouse.OverrideCursor = Cursors.Wait;

                if (StateDefinition.Details is cHealthCardDetailsValued)
                {
                    if (_valuedDetailsData == null)
                        return false;
                }
                else
                {
                    if (!await LoadDetailsDataAsync())
                        return false;
                }

                cDetailedDataModel detailedData = null;
                cDetailedChartModel detailedChartData = null;
                cChartModel chartData = null;
                if (StateDefinition.Details is cHealthCardDetailsValued)
                {
                    detailedData = GetValuedDataModel();
                }
                else
                {
                    detailedData = GetDetailedData();
                    detailedChartData = GetDetailedChartData();
                    chartData = GetChartData();
                }
                cRecommendationDataModel recommendationDataModel = Recommendation != null ? new cRecommendationDataModel() { Category = DetailsTitle, Recommendation = Recommendation } : null;

                if (decorator.Child is DataCanvas existingDataCanvas)
                {
                    var tempDataCanvasData = new cDataCanvasDataModel() { QuickActionStatusMonitorData = null, DetailedData = detailedData, DetailedChartData = detailedChartData, ChartData = chartData, RecommendationData = recommendationDataModel };
                    if (!(StateDefinition.Details is cHealthCardDetailsValued))
                    {
                        tempDataCanvasData.GetDetailedDataAsync = GetRefreshedDetailedDataAsync;
                        tempDataCanvasData.GetChartDataAsync = GetRefreshedChartDataAsync;
                        tempDataCanvasData.GetDatailedChartDataAsync = GetRefreshedDetailsChartDataAsync;
                    }

                    existingDataCanvas.DataCanvasData = tempDataCanvasData;
                    existingDataCanvas.IsDetailedLayout = isDetailedLayout;
                    existingDataCanvas.DataProvider = DataProvider;
                    existingDataCanvas.Visibility = Visibility.Visible;
                }
                else
                {
                    DataCanvas dataCanvas = new DataCanvas() { DataCanvasData = new cDataCanvasDataModel() { DetailedData = detailedData, DetailedChartData = detailedChartData, ChartData = chartData, RecommendationData = recommendationDataModel }, IsDetailedLayout = isDetailedLayout, DataProvider = DataProvider };
                    if (!(StateDefinition.Details is cHealthCardDetailsValued))
                    {
                        dataCanvas.DataCanvasData.GetDetailedDataAsync = GetRefreshedDetailedDataAsync;
                        dataCanvas.DataCanvasData.GetChartDataAsync = GetRefreshedChartDataAsync;
                        dataCanvas.DataCanvasData.GetDatailedChartDataAsync = GetRefreshedDetailsChartDataAsync;
                    }

                    decorator.Child = dataCanvas;
                }

                decorator.Visibility = Visibility.Visible;

                return true;
            }
            catch (Exception E)
            {
                LogException(E);
            }
            finally
            {
                Mouse.OverrideCursor = null;
            }
            return false;
        }

        private async Task<bool> LoadDetailsDataAsync()
        {
            try
            {
                if (StateDefinition == null || StateDefinition.Details == null)
                    return false;

                if (!(StateDefinition.Details is cHealthCardDetails _details) || _details.Table == null)
                    return false;

                if (CachedDetails.TryGetValue(DataProvider.Identities, out var cachedDetailsForIdentities))
                {
                    if (cachedDetailsForIdentities.TryGetValue(_details.Table, out _))
                        return true;
                }
                else
                    cachedDetailsForIdentities = new Dictionary<string, cF4SDHealthCardRawData.cHealthCardDetailsTable>();

                cF4sdHealthCardRawDataRequest requestData = new cF4sdHealthCardRawDataRequest()
                {
                    Identities = DataProvider.Identities,
                    MaxAge = cF4SDCockpitXmlConfig.Instance.HealthCardConfig.SearchResultAge,
                    Tables = StateDefinition.GetRequiredTables()
                };

                var detailsData = await cFasdCockpitCommunicationBase.Instance.GetDetailsData(requestData);

                if (detailsData.Any(data => data.Name == _details.Table) || detailsData.Any(data => data.Name == StateDefinition.DetailedChartDetails?.ValueTable) || detailsData.Any(data => data.Name == StateDefinition.ChartDetails?.ValueTable))
                {
                    if (StateDefinition.Details != null && detailsData.Any(data => data.Name == _details.Table))
                    {
                        if (!cachedDetailsForIdentities.ContainsKey(_details.Table))
                            cachedDetailsForIdentities.Add(_details.Table, detailsData.First(data => data.Name == _details.Table));
                        if (!CachedDetails.ContainsKey(DataProvider.Identities))
                            CachedDetails.Add(DataProvider.Identities, cachedDetailsForIdentities);
                    }
                    if (StateDefinition.DetailedChartDetails != null && detailsData.Any(data => data.Name == StateDefinition.DetailedChartDetails.ValueTable))
                    {
                        if (!cachedDetailsForIdentities.ContainsKey(StateDefinition.DetailedChartDetails.ValueTable))
                            cachedDetailsForIdentities.Add(StateDefinition.DetailedChartDetails.ValueTable, detailsData.First(data => data.Name == StateDefinition.DetailedChartDetails.ValueTable));
                        if (!CachedDetails.ContainsKey(DataProvider.Identities))
                            CachedDetails.Add(DataProvider.Identities, cachedDetailsForIdentities);
                    }
                    if (StateDefinition.ChartDetails != null && detailsData.Any(data => data.Name == StateDefinition.ChartDetails.ValueTable))
                    {
                        if (!cachedDetailsForIdentities.ContainsKey(StateDefinition.ChartDetails.ValueTable))
                            cachedDetailsForIdentities.Add(StateDefinition.ChartDetails.ValueTable, detailsData.First(data => data.Name == StateDefinition.ChartDetails.ValueTable));
                        if (!CachedDetails.ContainsKey(DataProvider.Identities))
                            CachedDetails.Add(DataProvider.Identities, cachedDetailsForIdentities);
                    }
                }
                else
                    return false;

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

            return false;
        }

        private void UpdateProcessorStateDefinition()
        {
            // correct an error in older definitions of table agnt-computer-event-details-processorUsage
            try
            {
                var oldFromColumn = StateDefinition.Details.FirstOrDefault(column => column.Name == "From");
                var oldToColumn = StateDefinition.Details.FirstOrDefault(column => column.Name == "To");

                if (oldFromColumn is null || oldToColumn is null)
                    return;

                if (oldFromColumn.Column != "Duration" || oldToColumn.Column != "time")
                    return;

                oldFromColumn.Column = "time";
                oldToColumn.Column = "Duration.add";
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        private void ProcessDetailValue(string processintType, ref object rawValue, DateTime timeColumnValue)
        {
            try
            {
                if (rawValue is Int64 milliSec)
                {
                    switch (processintType)
                    {
                        case "add":
                            rawValue = timeColumnValue.AddMilliseconds(milliSec);
                            break;
                        case "sub":
                            rawValue = timeColumnValue.AddMilliseconds(-milliSec);
                            break;
                    }
                }
            }
            catch (Exception E)
            {
                LogException(E);
            }
        }

        private cDetailedDataModel GetValuedDataModel()
        {
            if (!(StateDefinition.Details is cHealthCardDetailsValued details) || _valuedDetailsData == null)
                return null;

            var _colCount = details.Count;

            if (_colCount == 0)
                return null;

            var rowHeadings = details.Select(v => (object)v.Names.GetValue()).ToList();
            cDetailedDataModel output = new cDetailedDataModel()
            {
                Heading = StateDefinition.Names.GetValue(),
                FullDetailedData = new List<object>(),
                HasColumnHeaders = _colCount > 1
            };

            if (rowHeadings.Count > 1)
                output.FullDetailedData.Add(rowHeadings);

            var _data = _valuedDetailsData.Values[0];
            foreach (var _row in _data)
            {
                var _rowValues = new List<object>();
                for (int i = 0; i < _colCount; i++)
                {
                    _rowValues.Add(_row[i]);
                }
                output.FullDetailedData.Add(_rowValues);
            }

            var _json = Newtonsoft.Json.JsonConvert.SerializeObject(output);

            return output;
        }

        private cDetailedDataModel GetDetailedData()
        {
            try
            {
                const string timeColumnName = "time";
                cDetailedDataModel output = new cDetailedDataModel() { FullDetailedData = new List<object>() };

                string dateAddtionFormat = cMultiLanguageSupport.GetItem("DetailsPage.History.Details");
                string dateAddition = string.Format(dateAddtionFormat, DateTime.Today.AddDays(-DayIndex).ToString("d", new CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage)));
                string detailedDataHeading = "Details - " + DetailsTitle + dateAddition;
                output.Heading = detailedDataHeading;

                if (!(StateDefinition.Details is cHealthCardDetails _details) || _details.Table == null)
                    return null;

                // correct an error in older definitions of table agnt-computer-event-details-processorUsage
                if (_details.Table == "agnt-computer-event-details-processorUsage")
                    UpdateProcessorStateDefinition();

                var headerValues = StateDefinition.Details.Select(value => value.Names.GetValue());
                output.FullDetailedData.Add(headerValues.ToList<object>());

                var detailsData = CachedDetails[DataProvider.Identities][_details.Table];

                if (!detailsData.Values.TryGetValue(DayIndex, out var details))
                    return output;

                int columnCount = StateDefinition.Details.Count;
                List<int> rawColumnIndexes = new List<int>(columnCount);
                List<RawValueType> displayTypes = new List<RawValueType>(columnCount);
                List<string> valueProcessings = new List<string>(columnCount);
                int timeColumnIndex = detailsData.Columns.IndexOf(timeColumnName);
                int filterColumnIndex = detailsData.Columns.IndexOf(StateDefinition.Details?.Filter?.FilterColumn);

                foreach (var columnDefinition in StateDefinition.Details)
                {
                    var splitColumnName = columnDefinition.Column.Split('.');

                    var rawColumnIndex = detailsData.Columns.IndexOf(splitColumnName[0]);
                    var displayType = columnDefinition.DisplayType;
                    var valueProcessing = splitColumnName.Length > 1 ? splitColumnName[1] : null;

                    rawColumnIndexes.Add(rawColumnIndex);
                    displayTypes.Add(displayType);
                    valueProcessings.Add(valueProcessing);
                }

                foreach (var detailsRow in details)
                {
                    var displayValuesOfRow = new List<object>(columnCount);

                    var displayTypeEnumerator = displayTypes.GetEnumerator();
                    var valueProcessingsEnumerator = valueProcessings.GetEnumerator();
                    bool shouldHideRow = false;

                    if (StateDefinition.Details.Filter != null && !rawColumnIndexes.Contains(filterColumnIndex))
                    {
                        bool columnValueMatchesFilterValue = StateDefinition.Details.Filter.FilterValues.Contains(detailsRow[filterColumnIndex]);
                        if (!columnValueMatchesFilterValue)
                            shouldHideRow = true;
                    }

                    cUtility.RawValueFormatter.SetDefaultCulture(new CultureInfo(cFasdCockpitConfig.Instance.SelectedLanguage));
                    cUtility.RawValueFormatter.SetDefaultTimeZone(TimeZoneInfo.Local);

                    foreach (var index in rawColumnIndexes)
                    {
                        try
                        {
                            if (shouldHideRow)
                                continue;

                            if (detailsRow.Length < index || !displayTypeEnumerator.MoveNext() || !valueProcessingsEnumerator.MoveNext())
                                continue;

                            var rawDetailValue = detailsRow[index];

                            if (index == filterColumnIndex
                                && !StateDefinition.Details.Filter.FilterValues.Contains(rawDetailValue))
                            {
                                shouldHideRow = true;
                                continue;
                            }

                            if (valueProcessingsEnumerator.Current != null)
                                if (detailsRow[timeColumnIndex] is DateTime timeColumnValue)
                                    ProcessDetailValue(valueProcessingsEnumerator.Current, ref rawDetailValue, timeColumnValue);

                            var displayValue = cUtility.RawValueFormatter.GetDisplayValue(rawDetailValue, displayTypeEnumerator.Current);
                            displayValuesOfRow.Add(displayValue);
                        }
                        catch (Exception E)
                        {
                            LogException(E);
                        }
                    }

                    if (shouldHideRow)
                        continue;

                    output.FullDetailedData.Add(displayValuesOfRow);
                }

                var _json = Newtonsoft.Json.JsonConvert.SerializeObject(output);

                return output;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        private cDetailedChartModel GetDetailedChartData()
        {
            try
            {
                if (StateDefinition?.DetailedChartDetails is null)
                    return null;

                var detailsDataTable = CachedDetails[DataProvider.Identities][StateDefinition.DetailedChartDetails.ValueTable];

                int valueIndex = detailsDataTable.Columns.IndexOf(StateDefinition.DetailedChartDetails.ValueColumn);
                int timeIndex = detailsDataTable.Columns.IndexOf(StateDefinition.DetailedChartDetails.TimeColumn);
                int durationIndex = detailsDataTable.Columns.IndexOf(StateDefinition.DetailedChartDetails.DurationColumn);
                string title = StateDefinition.Names.GetValue();
                bool isThresholdActive = StateDefinition is cHealthCardStateLevel;

                var output = new cDetailedChartModel()
                {
                    Data = detailsDataTable.Values[DayIndex],
                    ValueIndex = valueIndex,
                    TimeIndex = timeIndex,
                    DurationIndex = durationIndex,
                    ChartTitle = title,
                    IsThresholdActive = isThresholdActive,
                };

                if (StateDefinition is cHealthCardStateLevel stateLevelDefinition)
                {
                    output.WarningThreshold = stateLevelDefinition.Warning;
                    output.ErrorThreshold = stateLevelDefinition.Error;
                }
                return output;
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        private cChartModel GetChartData()
        {
            try
            {
                if (StateDefinition?.ChartDetails is null)
                    return null;

                var detailsDataTable = CachedDetails[DataProvider.Identities][StateDefinition.ChartDetails.ValueTable];

                int valueIndex = detailsDataTable.Columns.IndexOf(StateDefinition.ChartDetails.ValueColumn);
                int timeIndex = detailsDataTable.Columns.IndexOf(StateDefinition.ChartDetails.TimeColumn);
                int durationIndex = detailsDataTable.Columns.IndexOf(StateDefinition.ChartDetails.DurationColumn);
                string title = StateDefinition.Names.GetValue();
                bool isThresholdActive = false;
                double warningThreshold = 0;
                double errorThreshold = 0;
                bool isDirectionUp = true;
                if (StateDefinition is cHealthCardStateLevel stateLevel)
                {
                    isThresholdActive = true;
                    warningThreshold = stateLevel.Warning;
                    errorThreshold = stateLevel.Error;
                    isDirectionUp = stateLevel.IsDirectionUp;
                }
                int stepLengthScale = StateDefinition.ChartDetails.StepLengthScale;
                int stepLengthLine = StateDefinition.ChartDetails.StepLengthLine;
                int maxValue = StateDefinition.ChartDetails.MaxValue;
                int minValue = StateDefinition.ChartDetails.MinValue;
                string unitFormat = StateDefinition.UnitFormat;


                var output = new cChartModel()
                {
                    Data = detailsDataTable.Values[DayIndex],
                    ValueIndex = valueIndex,
                    TimeIndex = timeIndex,
                    DurationIndex = durationIndex,
                    ChartTitle = title,
                    WarningThreshold = warningThreshold,
                    ErrorThreshold = errorThreshold,
                    IsThresholdActive = isThresholdActive,
                    IsDirectionUp = isDirectionUp,
                    StepLengthScale = stepLengthScale,
                    StepLengthLine = stepLengthLine,
                    MaxValue = maxValue,
                    MinValue = minValue,
                    UnitFormat = unitFormat,

                };

                return output;

            }
            catch (Exception E)
            {
                LogException(E);
            }

            return null;
        }

        private async Task<cDetailedDataModel> GetRefreshedDetailedDataAsync()
        {
            cDetailedDataModel output = null;
            try
            {
                CachedDetails.Remove(DataProvider.Identities);

                if (await LoadDetailsDataAsync())
                    return GetDetailedData();
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return output;
        }

        private async Task<cDetailedChartModel> GetRefreshedDetailsChartDataAsync()
        {
            cDetailedChartModel output = null;

            try
            {
                CachedDetails.Remove(DataProvider.Identities);

                if (await LoadDetailsDataAsync())
                    return GetDetailedChartData();
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return output;
        }

        private async Task<cChartModel> GetRefreshedChartDataAsync()
        {
            cChartModel output = null;

            try
            {
                CachedDetails.Remove(DataProvider.Identities);

                if (await LoadDetailsDataAsync())
                    return GetChartData();
            }
            catch (Exception E)
            {
                LogException(E);
            }

            return output;
        }

    }
}
