using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using C4IT.FASD.Base;
using C4IT.FASD.Cockpit.Communication;
using C4IT.Logging;
using FasdDesktopUi.Basics.Models;
using FasdDesktopUi.Basics.Services.Models;
#if isDemo
using System.Net;
using FasdCockpitCommunicationDemo;
using System.Text.RegularExpressions;
#endif
using static C4IT.Logging.cLogManager;

namespace FasdDesktopUi.Basics.Services
{
    public sealed class TicketOverviewUpdateService
    {
        #region Fields

        private readonly ITicketOverviewCommunicationSource _communicationSource;
        private static readonly string[] OverviewKeys = new[]
        {
            "TicketsNew",
            "TicketsActive",
            "TicketsCritical",
            "TicketsNewInfo",
            "IncidentNew",
            "IncidentActive",
            "IncidentCritical",
            "IncidentNewInfo",
            "UnassignedTickets",
            "UnassignedTicketsCritical"
        };
        private readonly ITicketOverviewDispatcher _dispatcher;
        private readonly ITicketOverviewSettingsProvider _settingsProvider;
        private readonly Dictionary<string, TileCounts> _currentCounts = new Dictionary<string, TileCounts>(StringComparer.OrdinalIgnoreCase);
        private readonly Dictionary<(string Key, bool UseRoleScope), List<cF4sdApiSearchResultRelation>> _demoRelations = new Dictionary<(string, bool), List<cF4sdApiSearchResultRelation>>();
        private readonly HashSet<TileScope> _pendingScopes = new HashSet<TileScope>();
        private readonly HashSet<TileScope> _initializedScopes = new HashSet<TileScope>();
        private readonly object _fetchLock = new object();
        private readonly HashSet<TileScope> _retryScopes = new HashSet<TileScope>();
        private ITicketOverviewTimer _personalTimer;
        private ITicketOverviewTimer _roleTimer;
        private Task _fetchWorker;
        private bool _retryScheduled;
        private bool _isDemo;
        private bool _initialized;
        private bool _isEnabled;
        private readonly Random _random = new Random();
#if isDemo
        private readonly List<DemoTicketRecord> _persistedDemoTickets = new List<DemoTicketRecord>();
        private readonly List<DemoTicketTemplate> _demoTemplates = new List<DemoTicketTemplate>();
        private readonly HashSet<string> _usedSummaries = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        private const int SimulationHotkeyDelayMs = 400;
        private int _pendingSimulations;
        private ITicketOverviewTimer _simulationFlushTimer;
#endif

        #endregion

        #region Construction and Singleton

        private TicketOverviewUpdateService()
            : this(
                new TicketOverviewCommunicationSource(),
                new TicketOverviewDispatcher(Application.Current?.Dispatcher ?? Dispatcher.CurrentDispatcher),
                new TicketOverviewSettingsProvider())
        {
        }

        internal TicketOverviewUpdateService(
            ITicketOverviewCommunicationSource communicationSource,
            ITicketOverviewDispatcher dispatcher)
            : this(communicationSource, dispatcher, new TicketOverviewSettingsProvider())
        {
        }

        internal TicketOverviewUpdateService(
            ITicketOverviewCommunicationSource communicationSource,
            ITicketOverviewDispatcher dispatcher,
            ITicketOverviewSettingsProvider settingsProvider)
        {
            _communicationSource = communicationSource ?? throw new ArgumentNullException(nameof(communicationSource));
            _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
            _settingsProvider = settingsProvider ?? throw new ArgumentNullException(nameof(settingsProvider));
            foreach (var key in OverviewKeys)
            {
                _currentCounts[key] = TileCounts.Empty;
            }
#if isDemo
            _simulationFlushTimer = _dispatcher.CreateTimer(TimeSpan.FromMilliseconds(SimulationHotkeyDelayMs), SimulationFlushTimer_Tick);
#endif
        }

        private static readonly Lazy<TicketOverviewUpdateService> _instance =
            new Lazy<TicketOverviewUpdateService>(() => new TicketOverviewUpdateService());

        public static TicketOverviewUpdateService Instance => _instance.Value;

        #endregion

        #region Public API

        public event EventHandler<TicketOverviewCountsChangedEventArgs> OverviewCountsChanged;

        public IReadOnlyDictionary<string, TileCounts> CurrentCounts => _currentCounts;

        public bool IsScopeInitialized(TileScope scope)
        {
            lock (_fetchLock)
            {
                return _initializedScopes.Contains(scope);
            }
        }

        public bool AreAllScopesInitialized
        {
            get
            {
                lock (_fetchLock)
                {
                    return _initializedScopes.Contains(TileScope.Personal)
                        && _initializedScopes.Contains(TileScope.Role);
                }
            }
        }

        public void Start()
        {
            UpdateAvailability(true);
        }

        public void Stop()
        {
            UpdateAvailability(false);
        }

        #endregion

        #region Lifecycle

        public void UpdateAvailability(bool isEnabled)
        {
            if (isEnabled)
            {
                if (!_isEnabled)
                {
                    _isEnabled = true;
                    StartInternal();
                }
                else
                {
                    RefreshTimerIntervals();
                }
            }
            else
            {
                if (_isEnabled)
                    StopInternal();

                _isEnabled = false;
            }
        }

        private void StartInternal()
        {
            if (_initialized)
                return;

            _initialized = true;
#if isDemo
            _isDemo = true;
            LoadPersistedDemoTickets();
#else
            _isDemo = _communicationSource.Resolve()?.IsDemo() == true;
#endif

            InitializeTimers();
            _ = FetchAsync();
        }

        private void StopInternal()
        {
            if (!_initialized)
                return;

            _initialized = false;
            lock (_fetchLock)
            {
                _pendingScopes.Clear();
                _initializedScopes.Clear();
            }

            lock (_retryScopes)
            {
                _retryScheduled = false;
                _retryScopes.Clear();
            }

            _ = _dispatcher.InvokeAsync(() =>
            {
                _personalTimer?.Stop();
                _roleTimer?.Stop();
                _personalTimer = null;
                _roleTimer = null;

                foreach (var key in OverviewKeys)
                {
                    _currentCounts[key] = TileCounts.Empty;
                }
            });
        }

        #endregion

        #region Polling and Fetch Pipeline

        private void InitializeTimers()
        {
            _personalTimer = CreateScopeTimer(TileScope.Personal);
            _roleTimer = CreateScopeTimer(TileScope.Role);

            _personalTimer?.Start();
            _roleTimer?.Start();
        }

        private ITicketOverviewTimer CreateScopeTimer(TileScope scope)
        {
            var interval = GetPollingInterval(scope);
            return _dispatcher.CreateTimer(interval, () => _ = FetchAsync(scope));
        }

        private TimeSpan GetPollingInterval(TileScope scope)
        {
            int minutes = _settingsProvider.GetPollingMinutes(scope);
            return TimeSpan.FromMinutes(minutes);
        }

        public Task FetchAsync()
        {
            if (!_isEnabled)
                return Task.CompletedTask;

            return QueueFetchAsync(new[] { TileScope.Personal, TileScope.Role });
        }

        public Task FetchAsync(TileScope scope)
        {
            if (!_isEnabled)
                return Task.CompletedTask;

            return QueueFetchAsync(new[] { scope });
        }

        private Task QueueFetchAsync(IEnumerable<TileScope> scopes)
        {
            if (!_isEnabled)
                return Task.CompletedTask;

            if (scopes == null)
                return Task.CompletedTask;

            lock (_fetchLock)
            {
                foreach (var scope in scopes)
                {
                    _pendingScopes.Add(scope);
                }

                if (_fetchWorker == null || _fetchWorker.IsCompleted)
                {
                    _fetchWorker = Task.Run(ProcessFetchQueueAsync);
                }

                return _fetchWorker;
            }
        }

        private async Task ProcessFetchQueueAsync()
        {
            while (true)
            {
                TileScope scope;

                lock (_fetchLock)
                {
                    if (_pendingScopes.Count == 0)
                    {
                        _fetchWorker = null;
                        return;
                    }

                    scope = _pendingScopes.First();
                    _pendingScopes.Remove(scope);
                }

                await FetchScopeAsync(scope).ConfigureAwait(false);
            }
        }

        private async Task FetchScopeAsync(TileScope scope)
        {
            MethodBase CM = null;
            if (cLogManager.DefaultLogger.IsDebug)
            {
                CM = MethodBase.GetCurrentMethod();
                LogMethodBegin(CM);
            }

            try
            {
                if (!_isEnabled)
                    return;

                var communication = _communicationSource.Resolve();
                if (communication == null)
                {
                    ScheduleFetchRetry(scope);
                    return;
                }

                _isDemo = communication.IsDemo();

                var rawCounts = await communication.GetTicketOverviewCounts(OverviewKeys, scope == TileScope.Role).ConfigureAwait(false);
                var counts = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

                if (rawCounts != null)
                {
                    foreach (var kvp in rawCounts)
                    {
                        if (string.IsNullOrWhiteSpace(kvp.Key))
                            continue;

                        counts[kvp.Key] = Math.Max(0, kvp.Value);
                    }
                }

                if (!_isEnabled)
                    return;

                await _dispatcher.InvokeAsync(() => ProcessScopeCounts(scope, counts));
            }
            catch (Exception ex)
            {
                LogException(ex);
                ScheduleFetchRetry(scope);
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        #endregion

        #region Count Processing

        private void RefreshTimerIntervals()
        {
            _ = _dispatcher.InvokeAsync(() =>
            {
                if (_personalTimer != null)
                    _personalTimer.Interval = GetPollingInterval(TileScope.Personal);

                if (_roleTimer != null)
                    _roleTimer.Interval = GetPollingInterval(TileScope.Role);
            });
        }

        private void ProcessScopeCounts(TileScope scope, IDictionary<string, int> newCounts)
        {
            if (newCounts == null)
                return;

            bool hasInitializedScope;
            lock (_fetchLock)
            {
                hasInitializedScope = _initializedScopes.Contains(scope);
            }

            var result = TicketOverviewCountProcessor.Calculate(_currentCounts, OverviewKeys, scope, newCounts, hasInitializedScope);
            _currentCounts.Clear();
            foreach (var kvp in result.UpdatedCounts)
            {
                _currentCounts[kvp.Key] = kvp.Value;
            }

            if (result.IsInitialization)
            {
                lock (_fetchLock)
                {
                    _initializedScopes.Add(scope);
                }

                var initArgs = new TicketOverviewCountsChangedEventArgs(
                    Array.Empty<TileCountChange>(),
                    new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase),
                    scope);
                OverviewCountsChanged?.Invoke(this, initArgs);
                return;
            }

            if (result.Changes.Count == 0)
                return;

            var args = new TicketOverviewCountsChangedEventArgs(result.Changes, new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase));
            OverviewCountsChanged?.Invoke(this, args);
        }

        #endregion

        #region Demo - Public

        public void SimulateDemoTicket()
        {
            _isDemo = _communicationSource.Resolve()?.IsDemo() == true;
            if (!_isDemo)
                return;

#if isDemo
            if (_demoTemplates.Count == 0)
            {
                LoadDemoTemplates();
                if (_demoTemplates.Count == 0)
                    return;
            }

            Interlocked.Increment(ref _pendingSimulations);

            if (_simulationFlushTimer != null)
            {
                _simulationFlushTimer.Stop();
                _simulationFlushTimer.Interval = TimeSpan.FromMilliseconds(SimulationHotkeyDelayMs);
                _simulationFlushTimer.Start();
            }
            else
            {
                var count = Interlocked.Exchange(ref _pendingSimulations, 0);
                ProcessDemoSimulations(count);
            }
#endif
        }

        public IEnumerable<cF4sdApiSearchResultRelation> GetDemoRelations(string key, bool useRoleScope)
        {
            if (!_isDemo)
                return Enumerable.Empty<cF4sdApiSearchResultRelation>();

            lock (_demoRelations)
            {
                if (_demoRelations.TryGetValue((key, useRoleScope), out var list))
                    return list.ToList();
            }

            return Enumerable.Empty<cF4sdApiSearchResultRelation>();
        }

        #endregion

#if isDemo
        #region Demo - Internals

        private void LoadPersistedDemoTickets()
        {
            var data = TicketOverviewDataStore.LoadData();

            _demoTemplates.Clear();
            if (data.Templates != null)
                _demoTemplates.AddRange(data.Templates);

            _persistedDemoTickets.Clear();
            _usedSummaries.Clear();
            if (data.Tickets == null)
                return;

            foreach (var record in data.Tickets)
            {
                if (!string.IsNullOrWhiteSpace(record.Summary))
                    _usedSummaries.Add(record.Summary);

                _persistedDemoTickets.Add(record);
                AddRelationForRecord(record);
            }
        }

        private void LoadDemoTemplates()
        {
            var templates = TicketOverviewDataStore.LoadTemplates();
            if (templates == null || templates.Count == 0)
                return;

            _demoTemplates.Clear();
            _demoTemplates.AddRange(templates);
        }

        private void SimulationFlushTimer_Tick()
        {
            _simulationFlushTimer.Stop();
            var count = Interlocked.Exchange(ref _pendingSimulations, 0);
            ProcessDemoSimulations(count);
        }

        private void ProcessDemoSimulations(int count)
        {
            if (count <= 0)
                return;

            if (_demoTemplates.Count == 0)
            {
                LoadDemoTemplates();
                if (_demoTemplates.Count == 0)
                    return;
            }

            var appliedChanges = new List<TileCountChange>();

            for (int i = 0; i < count; i++)
            {
                var template = _demoTemplates[_random.Next(_demoTemplates.Count)];
                var record = CreateDemoTicketRecord(template);
                if (record == null)
                    continue;

                if (!TicketOverviewDataStore.AppendTicket(record))
                    continue;

                var change = RegisterDemoTicket(record);
                if (change.HasValue)
                    appliedChanges.Add(change.Value);
            }

            if (appliedChanges.Count == 0)
                return;

            var args = new TicketOverviewCountsChangedEventArgs(
                appliedChanges,
                new Dictionary<string, TileCounts>(_currentCounts, StringComparer.OrdinalIgnoreCase));
            OverviewCountsChanged?.Invoke(this, args);
        }

        private void AddRelationForRecord(DemoTicketRecord record)
        {
            if (record == null)
                return;

            var relation = CreateRelationFromRecord(record);
            var scopeKey = (record.TileKey, record.UseRoleScope);

            lock (_demoRelations)
            {
                if (!_demoRelations.TryGetValue(scopeKey, out var list))
                {
                    list = new List<cF4sdApiSearchResultRelation>();
                    _demoRelations[scopeKey] = list;
                }

                if (list.Any(existing => existing.id == relation.id))
                    return;

                list.Add(relation);
            }
        }

        private cF4sdApiSearchResultRelation CreateRelationFromRecord(DemoTicketRecord record)
        {
            var activityType = ResolveDemoActivityType(record?.ActivityType);
            var relation = new cF4sdApiSearchResultRelation
            {
                Type = enumF4sdSearchResultClass.Ticket,
                DisplayName = record.DisplayName,
                Name = record.DisplayName,
                id = record.TicketId,
                Status = enumF4sdSearchResultStatus.Active,
                Infos = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
                {
                    ["Summary"] = record.Summary ?? string.Empty,
                    ["StatusId"] = record.StatusId ?? string.Empty,
                    ["UserDisplayName"] = record.UserDisplayName ?? string.Empty,
                    ["UserAccount"] = record.UserAccount ?? string.Empty,
                    ["UserDomain"] = record.UserDomain ?? string.Empty,
                    ["ActivityType"] = activityType
                },
                Identities = new cF4sdIdentityList
                {
                    new cF4sdIdentityEntry { Class = enumFasdInformationClass.Ticket, Id = record.TicketId },
                    new cF4sdIdentityEntry { Class = enumFasdInformationClass.User, Id = record.UserId }
                }
            };

            return relation;
        }

        private static string ResolveDemoActivityType(string configuredActivityType)
        {
            return string.IsNullOrWhiteSpace(configuredActivityType)
                ? null
                : configuredActivityType.Trim();
        }

        private DemoTicketDetail CloneDetail(DemoTicketDetail source)
        {
            if (source == null)
                return new DemoTicketDetail();

            return new DemoTicketDetail
            {
                AffectedUser = source.AffectedUser,
                Asset = source.Asset,
                Category = source.Category,
                Description = source.Description,
                DescriptionHtml = source.DescriptionHtml,
                Priority = source.Priority,
                Solution = source.Solution,
                SolutionHtml = source.SolutionHtml,
                Journal = source.Journal?.Select(entry => new DemoTicketJournalEntry
                {
                    Header = entry?.Header,
                    Description = entry?.Description,
                    DescriptionHtml = entry?.DescriptionHtml,
                    IsVisibleForUser = entry?.IsVisibleForUser ?? true,
                    CreationDate = entry?.CreationDate ?? default
                }).ToList() ?? new List<DemoTicketJournalEntry>()
            };
        }

        private TileCountChange? RegisterDemoTicket(DemoTicketRecord record)
        {
            if (record == null)
                return null;

            _persistedDemoTickets.Add(record);
            AddRelationForRecord(record);

            if (!string.IsNullOrWhiteSpace(record.Summary))
                _usedSummaries.Add(record.Summary);

            _communicationSource.Resolve()?.RegisterGeneratedTicket(record);

            if (!_currentCounts.TryGetValue(record.TileKey, out var previousCounts))
                previousCounts = TileCounts.Empty;

            TileCounts updatedCounts;
            int oldValue;
            int newValue;
            TileScope scope;

            if (record.UseRoleScope)
            {
                updatedCounts = new TileCounts(previousCounts.Personal, previousCounts.Role + 1);
                oldValue = previousCounts.Role;
                newValue = updatedCounts.Role;
                scope = TileScope.Role;
            }
            else
            {
                updatedCounts = new TileCounts(previousCounts.Personal + 1, previousCounts.Role);
                oldValue = previousCounts.Personal;
                newValue = updatedCounts.Personal;
                scope = TileScope.Personal;
            }

            _currentCounts[record.TileKey] = updatedCounts;

            return new TileCountChange(record.TileKey, scope, oldValue, newValue);
        }

        private string EnsureUniqueSummary(string preferredSummary)
        {
            if (string.IsNullOrWhiteSpace(preferredSummary))
                preferredSummary = "Demo Ticket";

            if (!_usedSummaries.Contains(preferredSummary))
            {
                _usedSummaries.Add(preferredSummary);
                return preferredSummary;
            }

            var nextFreeSummary = _demoTemplates
                .Select(t => t?.Summary)
                .Where(s => !string.IsNullOrWhiteSpace(s))
                .FirstOrDefault(s => !_usedSummaries.Contains(s));

            if (!string.IsNullOrWhiteSpace(nextFreeSummary))
            {
                _usedSummaries.Add(nextFreeSummary);
                return nextFreeSummary;
            }

            var baseSummary = preferredSummary;
            var suffix = 2;
            var candidate = baseSummary;
            while (_usedSummaries.Contains(candidate))
            {
                candidate = $"{baseSummary} #{suffix}";
                suffix++;
            }

            _usedSummaries.Add(candidate);
            return candidate;
        }


        private DemoTicketRecord CreateDemoTicketRecord(DemoTicketTemplate template)
        {
            if (template == null)
                return null;

            var relationId = Guid.NewGuid();
            var createdAt = DateTime.UtcNow;
            var prefix = string.IsNullOrWhiteSpace(template.DisplayNamePrefix) ? "TCK" : template.DisplayNamePrefix.Trim();
            prefix = prefix.ToUpperInvariant();
            var displayName = TicketOverviewDataStore.GetNextDisplayName(prefix);
            var summary = EnsureUniqueSummary(template.Summary ?? string.Empty);
            var detail = CloneDetail(template.Detail);

            if (string.IsNullOrWhiteSpace(detail.AffectedUser))
                detail.AffectedUser = template.UserDisplayName ?? "Ticket, Timo";

            if (string.IsNullOrWhiteSpace(detail.Description) && !string.IsNullOrWhiteSpace(detail.DescriptionHtml))
            {
                detail.Description = Regex.Replace(detail.DescriptionHtml, "<.*?>", string.Empty);
            }

            if (string.IsNullOrWhiteSpace(detail.Description))
                detail.Description = summary;

            if (string.IsNullOrWhiteSpace(detail.DescriptionHtml))
            {
                detail.DescriptionHtml = $"<p>{WebUtility.HtmlEncode(detail.Description)}</p>";
            }

            if (string.IsNullOrWhiteSpace(detail.Solution) && !string.IsNullOrWhiteSpace(detail.SolutionHtml))
            {
                detail.Solution = Regex.Replace(detail.SolutionHtml, "<.*?>", string.Empty);
            }

            if (string.IsNullOrWhiteSpace(detail.Solution))
                detail.Solution = string.Empty;

            if (string.IsNullOrWhiteSpace(detail.SolutionHtml) && !string.IsNullOrWhiteSpace(detail.Solution))
            {
                detail.SolutionHtml = $"<p>{WebUtility.HtmlEncode(detail.Solution)}</p>";
            }

            if (detail.Journal == null || detail.Journal.Count == 0)
            {
                detail.Journal = new List<DemoTicketJournalEntry>
                {
                    new DemoTicketJournalEntry
                    {
                        Header = "Ticket erstellt",
                        Description = detail.Description ?? "Automatisch generiertes Demoticket.",
                        DescriptionHtml = detail.DescriptionHtml ?? "<p>Automatisch generiertes Demoticket.</p>",
                        IsVisibleForUser = true,
                        CreationDate = createdAt
                    }
                };
            }

            foreach (var entry in detail.Journal)
            {
                if (entry.CreationDate == default)
                    entry.CreationDate = createdAt;
            }

            return new DemoTicketRecord
            {
                TicketId = relationId,
                TileKey = string.IsNullOrWhiteSpace(template.TileKey) ? "TicketsNew" : template.TileKey,
                UseRoleScope = template.UseRoleScope,
                ActivityType = ResolveDemoActivityType(template.ActivityType),
                DisplayName = displayName,
                Summary = summary,
                StatusId = string.IsNullOrWhiteSpace(template.StatusId) ? "New" : template.StatusId,
                UserDisplayName = template.UserDisplayName ?? detail.AffectedUser ?? "Ticket, Timo",
                UserAccount = template.UserAccount ?? "TT007",
                UserDomain = template.UserDomain ?? "CONTOSO",
                UserId = template.UserId ?? Guid.Parse("42c760d6-90e8-469f-b2fe-ac7d4cc6cb0a"),
                CreatedAt = createdAt,
                Detail = detail
            };
        }

#endregion
#endif

        #region Utilities

        public Dictionary<string, int> GetCountsForScope(bool useRoleScope)
        {
            return _currentCounts.ToDictionary(kvp => kvp.Key, kvp => useRoleScope ? kvp.Value.Role : kvp.Value.Personal, StringComparer.OrdinalIgnoreCase);
        }

        #endregion

        #region Retry Handling

        private void ScheduleFetchRetry(TileScope scope)
        {
            MethodBase CM = null;
            if (cLogManager.DefaultLogger.IsDebug)
            {
                CM = MethodBase.GetCurrentMethod();
                LogMethodBegin(CM);
            }

            try
            {
                if (!_isEnabled)
                    return;

                lock (_retryScopes)
                {
                    _retryScopes.Add(scope);
                    if (_retryScheduled)
                        return;

                    _retryScheduled = true;
                }

                _ = _dispatcher.InvokeAsync(async () =>
                {
                    try
                    {
                        await Task.Delay(TimeSpan.FromSeconds(5)).ConfigureAwait(false);
                        TileScope[] scopes;
                        lock (_retryScopes)
                        {
                            scopes = _retryScopes.ToArray();
                            _retryScopes.Clear();
                            _retryScheduled = false;
                        }

                        foreach (var pendingScope in scopes)
                        {
                            await FetchAsync(pendingScope).ConfigureAwait(false);
                        }
                    }
                    catch (Exception ex)
                    {
                        LogException(ex);
                    }
                });
            }
            catch (Exception ex)
            {
                LogException(ex);
            }
            finally
            {
                LogMethodEnd(CM);
            }
        }

        #endregion
    }
}
