using Dalamud.Interface;
using LightlessSync.API.Dto.Group;
using LightlessSync.API.Dto.User;
using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.Services.Mediator;
using LightlessSync.UI;
using LightlessSync.UI.Models;
using LightlessSync.Utils;
using LightlessSync.WebAPI;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace LightlessSync.Services.LightFinder;
public class LightFinderService : IHostedService, IMediatorSubscriber
{
    private readonly ILogger<LightFinderService> _logger;
    private readonly ApiController _apiController;
    private readonly LightlessMediator _mediator;
    private readonly LightlessConfigService _config;
    private readonly DalamudUtilService _dalamudUtil;
    private CancellationTokenSource? _lightfinderCancelTokens;
    private Action? _connectedHandler;
    public LightlessMediator Mediator => _mediator;

    public bool IsLightFinderAvailable { get; private set; } = false;

    public bool IsBroadcasting => _config.Current.BroadcastEnabled;
    private bool _syncedOnStartup = false;
    private bool _waitingForTtlFetch = false;
    private TimeSpan? _remainingTtl = null;
    private DateTime _lastTtlCheck = DateTime.MinValue;
    private DateTime _lastForcedDisableTime = DateTime.MinValue;
    private static readonly TimeSpan _disableCooldown = TimeSpan.FromSeconds(5);
    public TimeSpan? RemainingTtl => _remainingTtl;
    public TimeSpan? RemainingCooldown
    {
        get
        {
            var elapsed = DateTime.UtcNow - _lastForcedDisableTime;
            if (elapsed >= _disableCooldown) return null;
            return _disableCooldown - elapsed;
        }
    }

    public LightFinderService(ILogger<LightFinderService> logger, LightlessMediator mediator, LightlessConfigService config, DalamudUtilService dalamudUtil, ApiController apiController)
    {
        _logger = logger;
        _mediator = mediator;
        _config = config;
        _dalamudUtil = dalamudUtil;
        _apiController = apiController;
    }

    private async Task RequireConnectionAsync(string context, Func<Task> action)
    {
        if (!_apiController.IsConnected)
        {
            _logger.LogDebug("{context} skipped, not connected", context);
            return;
        }
        await action().ConfigureAwait(false);
    }

    private async Task<string?> GetLocalHashedCidAsync(string context)
    {
        try
        {
            var cid = _dalamudUtil.GetCID();
            return cid.ToString().GetHash256();
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to resolve CID for {Context}", context);
            return null;
        }
    }

    private void ApplyBroadcastDisabled(bool forcePublish = false)
    {
        bool wasEnabled = _config.Current.BroadcastEnabled;
        bool hadExpiry = _config.Current.BroadcastTtl != DateTime.MinValue;
        bool hadRemaining = _remainingTtl.HasValue;

        _config.Current.BroadcastEnabled = false;
        _config.Current.BroadcastTtl = DateTime.MinValue;

        if (wasEnabled || hadExpiry)
            _config.Save();

        _remainingTtl = null;
        _waitingForTtlFetch = false;
        _syncedOnStartup = false;

        if (forcePublish || wasEnabled || hadRemaining)
            _mediator.Publish(new BroadcastStatusChangedMessage(false, null));
    }

    private bool TryApplyBroadcastEnabled(TimeSpan? ttl, string context)
    {
        if (ttl is not { } validTtl || validTtl <= TimeSpan.Zero)
        {
            _logger.LogWarning("Lightfinder enable skipped ({Context}): invalid TTL ({TTL})", context, ttl);
            return false;
        }

        bool wasEnabled = _config.Current.BroadcastEnabled;
        TimeSpan? previousRemaining = _remainingTtl;
        DateTime previousExpiry = _config.Current.BroadcastTtl;

        var newExpiry = DateTime.UtcNow + validTtl;

        _config.Current.BroadcastEnabled = true;
        _config.Current.BroadcastTtl = newExpiry;

        if (!wasEnabled || previousExpiry != newExpiry)
            _config.Save();

        _remainingTtl = validTtl;
        _waitingForTtlFetch = false;

        if (!wasEnabled || previousRemaining != validTtl)
            _mediator.Publish(new BroadcastStatusChangedMessage(true, validTtl));

        _logger.LogInformation("Lightfinder broadcast enabled ({Context}), TTL: {TTL}", context, validTtl);
        return true;
    }

    private void HandleLightfinderUnavailable(string message, Exception? ex = null)
    {
        if (ex != null)
            _logger.LogWarning(ex, message);
        else
            _logger.LogWarning(message);

        IsLightFinderAvailable = false;
        ApplyBroadcastDisabled(forcePublish: true);
    }

    private void OnDisconnected()
    {
        IsLightFinderAvailable = false;
        ApplyBroadcastDisabled(forcePublish: true);
        _logger.LogDebug("Cleared Lightfinder state due to disconnect.");

    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _mediator.Subscribe<EnableBroadcastMessage>(this, OnEnableBroadcast);
        _mediator.Subscribe<BroadcastStatusChangedMessage>(this, OnBroadcastStatusChanged);
        _mediator.Subscribe<PriorityFrameworkUpdateMessage>(this, OnTick);
        _mediator.Subscribe<DisconnectedMessage>(this, _ => OnDisconnected());

        IsLightFinderAvailable = false;

        _lightfinderCancelTokens?.Cancel();
        _lightfinderCancelTokens?.Dispose();
        _lightfinderCancelTokens = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        _connectedHandler = () => _ = CheckLightfinderSupportAsync(_lightfinderCancelTokens.Token);
        _apiController.OnConnected += _connectedHandler;

        if (_apiController.IsConnected)
            _ = CheckLightfinderSupportAsync(_lightfinderCancelTokens.Token);

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _lightfinderCancelTokens?.Cancel();
        _lightfinderCancelTokens?.Dispose();
        _lightfinderCancelTokens = null;

        if (_connectedHandler is not null)
        {
            _apiController.OnConnected -= _connectedHandler;
            _connectedHandler = null;
        }

        _mediator.UnsubscribeAll(this);
        return Task.CompletedTask;
    }

    private async Task CheckLightfinderSupportAsync(CancellationToken cancellationToken)
    {
        try
        {
            while (!_apiController.IsConnected && !cancellationToken.IsCancellationRequested)
                await Task.Delay(250, cancellationToken).ConfigureAwait(false);

            if (cancellationToken.IsCancellationRequested)
                return;

            var hashedCid = await GetLocalHashedCidAsync("Lightfinder state check").ConfigureAwait(false);
            if (string.IsNullOrEmpty(hashedCid))
                return;

            BroadcastStatusInfoDto? status = null;
            try
            {
                status = await _apiController.IsUserBroadcasting(hashedCid).ConfigureAwait(false);
            }
            catch (HubException ex) when (ex.Message.Contains("Method does not exist", StringComparison.OrdinalIgnoreCase))
            {
                HandleLightfinderUnavailable("Lightfinder unavailable on server (required method missing).", ex);
            }

            if (!IsLightFinderAvailable)
                _logger.LogInformation("Lightfinder is available.");

            IsLightFinderAvailable = true;

            bool isBroadcasting = status?.IsBroadcasting == true;
            TimeSpan? ttl = status?.TTL;

            if (isBroadcasting)
            {
                if (ttl is not { } remaining || remaining <= TimeSpan.Zero)
                    ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);

                if (TryApplyBroadcastEnabled(ttl, "server handshake"))
                {
                    _syncedOnStartup = true;
                }
                else
                {
                    isBroadcasting = false;
                }
            }

            if (!isBroadcasting)
            {
                ApplyBroadcastDisabled(forcePublish: true);
                _logger.LogInformation("Lightfinder is available but no active broadcast was found.");
            }

            if (_config.Current.LightfinderAutoEnableOnConnect && !isBroadcasting)
            {
                _logger.LogInformation("Auto-enabling Lightfinder broadcast after reconnect.");
                _mediator.Publish(new EnableBroadcastMessage(hashedCid, true));
                
                _mediator.Publish(new NotificationMessage(
                    "Broadcast Auto-Enabled",
                    "Your Lightfinder broadcast has been automatically enabled.",
                    NotificationType.Info));
            }
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Lightfinder check was canceled.");
        }
        catch (Exception ex)
        {
            HandleLightfinderUnavailable("Lightfinder check failed.", ex);
        }
    }

    private void OnEnableBroadcast(EnableBroadcastMessage msg)
    {
        _ = RequireConnectionAsync(nameof(OnEnableBroadcast), async () =>
        {
            try
            {
                GroupBroadcastRequestDto? groupDto = null;
                if (_config.Current.SyncshellFinderEnabled && _config.Current.SelectedFinderSyncshell != null)
                {
                    groupDto = new GroupBroadcastRequestDto
                    {
                        HashedCID = msg.HashedCid,
                        GID = _config.Current.SelectedFinderSyncshell,
                        Enabled = msg.Enabled,
                    };
                }

                await _apiController.SetBroadcastStatus(msg.Enabled, groupDto).ConfigureAwait(false);

                _logger.LogDebug("Broadcast {Status} for {Cid}", msg.Enabled ? "enabled" : "disabled", msg.HashedCid);

                if (!msg.Enabled)
                {
                    ApplyBroadcastDisabled(forcePublish: true);
                    Mediator.Publish(new EventMessage(new Events.Event(nameof(LightFinderService), Services.Events.EventSeverity.Informational, $"Disabled Lightfinder for Player: {msg.HashedCid}")));
                    return;
                }

                _waitingForTtlFetch = true;

                try
                {
                    TimeSpan? ttl = await GetBroadcastTtlAsync(msg.HashedCid).ConfigureAwait(false);

                    if (TryApplyBroadcastEnabled(ttl, "client request"))
                    {
                        _logger.LogDebug("Fetched TTL from server: {TTL}", ttl);
                        Mediator.Publish(new EventMessage(new Events.Event(nameof(LightFinderService), Services.Events.EventSeverity.Informational, $"Enabled Lightfinder for Player: {msg.HashedCid}")));
                    }
                    else
                    {
                        ApplyBroadcastDisabled(forcePublish: true);
                        _logger.LogWarning("No valid TTL returned after enabling broadcast. Disabling.");
                    }
                }
                finally
                {
                    _waitingForTtlFetch = false;
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to toggle broadcast for {Cid}", msg.HashedCid);
                _waitingForTtlFetch = false;
            }
        });
    }

    private void OnBroadcastStatusChanged(BroadcastStatusChangedMessage msg)
    {
        _config.Current.BroadcastEnabled = msg.Enabled;
        _config.Save();
    }

    public async Task<bool> CheckIfBroadcastingAsync(string targetCid)
    {
        bool result = false;
        await RequireConnectionAsync(nameof(CheckIfBroadcastingAsync), async () =>
        {
            try
            {
                _logger.LogDebug("[BroadcastCheck] Checking CID: {cid}", targetCid);

                var info = await _apiController.IsUserBroadcasting(targetCid).ConfigureAwait(false);
                result = info?.TTL > TimeSpan.Zero;


                _logger.LogDebug("[BroadcastCheck] Result for {cid}: {result} (TTL: {ttl}, GID: {gid})", targetCid, result, info?.TTL, info?.GID);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to check broadcast status for {cid}", targetCid);
            }
        }).ConfigureAwait(false);


        return result;
    }

    public async Task<TimeSpan?> GetBroadcastTtlAsync(string? cidForLog = null)
    {
        TimeSpan? ttl = null;
        await RequireConnectionAsync(nameof(GetBroadcastTtlAsync), async () => {
            try
            {
                ttl = await _apiController.GetBroadcastTtl().ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                if (cidForLog is { Length: > 0 })
                {
                    _logger.LogWarning(ex, "Failed to fetch broadcast TTL for {Cid}", cidForLog);
                }
                else
                {
                    _logger.LogWarning(ex, "Failed to fetch broadcast TTL");
                }
            }
        }).ConfigureAwait(false);
        return ttl;
    }

    public async Task<Dictionary<string, BroadcastStatusInfoDto?>> AreUsersBroadcastingAsync(List<string> hashedCids)
    {
        Dictionary<string, BroadcastStatusInfoDto?> result = new(StringComparer.Ordinal);

        await RequireConnectionAsync(nameof(AreUsersBroadcastingAsync), async () =>
        {
            try
            {
                var batch = await _apiController.AreUsersBroadcasting(hashedCids).ConfigureAwait(false);

                if (batch?.Results != null)
                {
                    foreach (var kv in batch.Results)
                        result[kv.Key] = kv.Value;
                }

                _logger.LogTrace("Batch broadcast status check complete for {Count} CIDs", hashedCids.Count);
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Failed to batch check broadcast status");
            }
        }).ConfigureAwait(false);

        return result;
    }

    public async void ToggleBroadcast()
    {

        if (!IsLightFinderAvailable)
        {
            _logger.LogWarning("ToggleBroadcast - Lightfinder is not available.");
            _mediator.Publish(new NotificationMessage(
                "Broadcast Unavailable",
                "Lightfinder is not available on this server.",
                NotificationType.Error));
            return;
        }

        await RequireConnectionAsync(nameof(ToggleBroadcast), async () =>
        {
            var cooldown = RemainingCooldown;
            if (!_config.Current.BroadcastEnabled && cooldown is { } cd && cd > TimeSpan.Zero)
            {
                _logger.LogWarning("Cooldown active. Must wait {Remaining}s before re-enabling.", cd.TotalSeconds);
                _mediator.Publish(new NotificationMessage(
                    "Broadcast Cooldown",
                    $"Please wait {cd.TotalSeconds:F0} seconds before re-enabling broadcast.",
                    NotificationType.Warning));
                return;
            }

            var hashedCid = await GetLocalHashedCidAsync(nameof(ToggleBroadcast)).ConfigureAwait(false);
            if (string.IsNullOrEmpty(hashedCid))
            {
                _logger.LogWarning("ToggleBroadcast - unable to resolve CID.");
                return;
            }

            try
            {
                var isCurrentlyBroadcasting = await CheckIfBroadcastingAsync(hashedCid).ConfigureAwait(false);
                var newStatus = !isCurrentlyBroadcasting;

                if (!newStatus)
                {
                    _lastForcedDisableTime = DateTime.UtcNow;
                    _logger.LogDebug("Manual disable: cooldown timer started.");
                }

                _logger.LogDebug("Toggling broadcast. Server currently broadcasting: {ServerStatus}, setting to: {NewStatus}", isCurrentlyBroadcasting, newStatus);

                _mediator.Publish(new EnableBroadcastMessage(hashedCid, newStatus));
                
                _mediator.Publish(new NotificationMessage(
                    newStatus ? "Broadcast Enabled" : "Broadcast Disabled",
                    newStatus ? "Your Lightfinder broadcast has been enabled." : "Your Lightfinder broadcast has been disabled.",
                    NotificationType.Info));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to determine current broadcast status for toggle");
                _mediator.Publish(new NotificationMessage(
                    "Broadcast Toggle Failed",
                    $"Failed to toggle broadcast: {ex.Message}",
                    NotificationType.Error));
            }
        }).ConfigureAwait(false);
    }

    private async void OnTick(PriorityFrameworkUpdateMessage _)
    {
        if (!IsLightFinderAvailable)
            return;

        if (_config?.Current == null)
            return;

        if ((DateTime.UtcNow - _lastTtlCheck).TotalSeconds < 1)
            return;

        _lastTtlCheck = DateTime.UtcNow;

        await RequireConnectionAsync(nameof(OnTick), async () => {
            if (!_syncedOnStartup && _config.Current.BroadcastEnabled)
            {
                try
                {
                    var hashedCid = await GetLocalHashedCidAsync("startup TTL refresh").ConfigureAwait(false);
                    if (string.IsNullOrEmpty(hashedCid))
                    {
                        _logger.LogDebug("Skipping TTL refresh; hashed CID unavailable.");
                        return;
                    }

                    TimeSpan? ttl = await GetBroadcastTtlAsync(hashedCid).ConfigureAwait(false);
                    if (TryApplyBroadcastEnabled(ttl, "startup TTL refresh"))
                    {
                        _syncedOnStartup = true;
                        _logger.LogDebug("Refreshed broadcast TTL from server on first OnTick: {TTL}", ttl);
                    }
                    else
                    {
                        _logger.LogWarning("No valid TTL found on OnTick. Disabling broadcast state.");
                        ApplyBroadcastDisabled(forcePublish: true);
                    }
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Failed to refresh TTL in OnTick");
                    _syncedOnStartup = false;
                }
            }
            if (_config.Current.BroadcastEnabled)
            {
                if (_waitingForTtlFetch)
                {
                    _logger.LogDebug("OnTick skipped: waiting for TTL fetch");
                    return;
                }

                DateTime expiry = _config.Current.BroadcastTtl;
                TimeSpan remaining = expiry - DateTime.UtcNow;
                _remainingTtl = remaining > TimeSpan.Zero ? remaining : null;
                if (_remainingTtl == null)
                {
                    _logger.LogDebug("Broadcast TTL expired. Disabling broadcast locally.");
                    ApplyBroadcastDisabled(forcePublish: true);
                    ShowBroadcastExpiredNotification();
                }
            }
            else
            {
                _remainingTtl = null;
            }
        }).ConfigureAwait(false);
    }

    private void ShowBroadcastExpiredNotification()
    {
        var notification = new LightlessNotification
        {
            Id = "broadcast_expired",
            Title = "Broadcast Expired",
            Message = "Your Lightfinder broadcast has expired after 3 hours. Would you like to re-enable it?",
            Type = NotificationType.PairRequest,
            Duration = TimeSpan.FromSeconds(180),
            Actions = new List<LightlessNotificationAction>
            {
                new()
                {
                    Id = "re_enable",
                    Label = "Re-enable",
                    Icon = FontAwesomeIcon.Plus,
                    Color = UIColors.Get("PairBlue"),
                    IsPrimary = true,
                    OnClick = (n) =>
                    {
                        _logger.LogInformation("Re-enabling broadcast from notification");
                        ToggleBroadcast();
                        n.IsDismissed = true;
                        n.IsAnimatingOut = true;
                    }
                },
                new()
                {
                    Id = "close",
                    Label = "Close",
                    Icon = FontAwesomeIcon.Times,
                    Color = UIColors.Get("DimRed"),
                    OnClick = (n) =>
                    {
                        _logger.LogInformation("Broadcast expiration notification dismissed");
                        n.IsDismissed = true;
                        n.IsAnimatingOut = true;
                    }
                }
            }
        };

        _mediator.Publish(new LightlessNotificationMessage(notification));
    }
}