using LightlessSync.API.Data;
using LightlessSync.FileCache;
using LightlessSync.LightlessConfiguration;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services.Events;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.TextureCompression;
using LightlessSync.UI;
using LightlessSync.WebAPI.Files.Models;
using Microsoft.Extensions.Logging;

namespace LightlessSync.Services;

public class PlayerPerformanceService
{
    private readonly FileCacheManager _fileCacheManager;
    private readonly XivDataAnalyzer _xivDataAnalyzer;
    private readonly ILogger<PlayerPerformanceService> _logger;
    private readonly LightlessMediator _mediator;
    private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
    private readonly TextureDownscaleService _textureDownscaleService;
    private readonly Dictionary<string, bool> _warnedForPlayers = new(StringComparer.Ordinal);

    public PlayerPerformanceService(ILogger<PlayerPerformanceService> logger, LightlessMediator mediator,
        PlayerPerformanceConfigService playerPerformanceConfigService, FileCacheManager fileCacheManager,
        XivDataAnalyzer xivDataAnalyzer, TextureDownscaleService textureDownscaleService)
    {
        _logger = logger;
        _mediator = mediator;
        _playerPerformanceConfigService = playerPerformanceConfigService;
        _fileCacheManager = fileCacheManager;
        _xivDataAnalyzer = xivDataAnalyzer;
        _textureDownscaleService = textureDownscaleService;
    }

    public async Task<bool> CheckBothThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData)
    {
        var config = _playerPerformanceConfigService.Current;
        bool notPausedAfterVram = ComputeAndAutoPauseOnVRAMUsageThresholds(pairHandler, charaData, []);
        if (!notPausedAfterVram) return false;
        bool notPausedAfterTris = await CheckTriangleUsageThresholds(pairHandler, charaData).ConfigureAwait(false);
        if (!notPausedAfterTris) return false;

        if (config.UIDsToIgnore
            .Exists(uid => string.Equals(uid, pairHandler.UserData.Alias, StringComparison.Ordinal) || string.Equals(uid, pairHandler.UserData.UID, StringComparison.Ordinal)))
            return true;


        var vramUsage = pairHandler.LastAppliedApproximateVRAMBytes;
        var triUsage = pairHandler.LastAppliedDataTris;

        bool isPrefPerm = pairHandler.HasStickyPermissions;

        bool exceedsTris = CheckForThreshold(config.WarnOnExceedingThresholds, config.TrisWarningThresholdThousands * 1000L,
            triUsage, config.WarnOnPreferredPermissionsExceedingThresholds, isPrefPerm);
        bool exceedsVram = CheckForThreshold(config.WarnOnExceedingThresholds, config.VRAMSizeWarningThresholdMiB * 1024L * 1024L,
            vramUsage, config.WarnOnPreferredPermissionsExceedingThresholds, isPrefPerm);

        if (_warnedForPlayers.TryGetValue(pairHandler.UserData.UID, out bool hadWarning) && hadWarning)
        {
            _warnedForPlayers[pairHandler.UserData.UID] = exceedsTris || exceedsVram;
            return true;
        }

        _warnedForPlayers[pairHandler.UserData.UID] = exceedsTris || exceedsVram;

        if (exceedsVram)
        {
            _mediator.Publish(new EventMessage(new Event(pairHandler.PlayerName, pairHandler.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning,
                $"Exceeds VRAM threshold: ({UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeWarningThresholdMiB} MiB)")));
        }

        if (exceedsTris)
        {
            _mediator.Publish(new EventMessage(new Event(pairHandler.PlayerName, pairHandler.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning,
                $"Exceeds triangle threshold: ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)")));
        }

        if (exceedsTris || exceedsVram)
        {
            string warningText = string.Empty;
            if (exceedsTris && !exceedsVram)
            {
                warningText = $"Player {pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) exceeds your configured triangle warning threshold\n" +
                    $"{triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles";
            }
            else if (!exceedsTris)
            {
                warningText = $"Player {pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) exceeds your configured VRAM warning threshold\n" +
                    $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB";
            }
            else
            {
                warningText = $"Player {pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) exceeds both VRAM warning threshold and triangle warning threshold\n" +
                    $"{UiSharedService.ByteToString(vramUsage, true)}/{config.VRAMSizeWarningThresholdMiB} MiB and {triUsage}/{config.TrisWarningThresholdThousands * 1000} triangles";
            }

            _mediator.Publish(new PerformanceNotificationMessage(
                $"{pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) exceeds performance threshold(s)",
                warningText,
                pairHandler.UserData,
                pairHandler.IsPaused,
                pairHandler.PlayerName));
        }

        return true;
    }

    public async Task<bool> CheckTriangleUsageThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData)
    {
        var config = _playerPerformanceConfigService.Current;

        long triUsage = 0;

        if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? playerReplacements))
        {
            pairHandler.LastAppliedDataTris = 0;
            return true;
        }

        var moddedModelHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith("mdl", StringComparison.OrdinalIgnoreCase)))
            .Select(p => p.Hash)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

        foreach (var hash in moddedModelHashes)
        {
            triUsage += await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false);
        }

        pairHandler.LastAppliedDataTris = triUsage;

        _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler);

        // no warning of any kind on ignored pairs
        if (config.UIDsToIgnore
            .Exists(uid => string.Equals(uid, pairHandler.UserData.Alias, StringComparison.Ordinal) || string.Equals(uid, pairHandler.UserData.UID, StringComparison.Ordinal)))
            return true;

        bool isPrefPerm = pairHandler.HasStickyPermissions;

        // now check auto pause
        if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.TrisAutoPauseThresholdThousands * 1000L,
            triUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm))
        {
            var message = $"Player {pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) exceeded your configured triangle auto pause threshold and has been automatically paused\n" +
                $"{triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles";
            
            _mediator.Publish(new PerformanceNotificationMessage(
                $"{pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) automatically paused",
                message,
                pairHandler.UserData,
                true,
                pairHandler.PlayerName));

            _mediator.Publish(new EventMessage(new Event(pairHandler.PlayerName, pairHandler.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning,
                $"Exceeds triangle threshold: automatically paused ({triUsage}/{config.TrisAutoPauseThresholdThousands * 1000} triangles)")));

            _mediator.Publish(new PauseMessage(pairHandler.UserData));

            return false;
        }

        return true;
    }

    public bool ComputeAndAutoPauseOnVRAMUsageThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData, List<DownloadFileTransfer> toDownloadFiles)
    {
        var config = _playerPerformanceConfigService.Current;
        bool skipDownscale = pairHandler.IsDirectlyPaired && pairHandler.HasStickyPermissions;

        long vramUsage = 0;
        long effectiveVramUsage = 0;

        if (!charaData.FileReplacements.TryGetValue(API.Data.Enum.ObjectKind.Player, out List<FileReplacementData>? playerReplacements))
        {
            pairHandler.LastAppliedApproximateVRAMBytes = 0;
            pairHandler.LastAppliedApproximateEffectiveVRAMBytes = 0;
            return true;
        }

        var moddedTextureHashes = playerReplacements.Where(p => string.IsNullOrEmpty(p.FileSwapPath) && p.GamePaths.Any(g => g.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)))
            .Select(p => p.Hash)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToList();

        foreach (var hash in moddedTextureHashes)
        {
            long fileSize = 0;
            long effectiveSize = 0;

            var download = toDownloadFiles.Find(f => string.Equals(hash, f.Hash, StringComparison.OrdinalIgnoreCase));
            if (download != null)
            {
                fileSize = download.TotalRaw;
                effectiveSize = fileSize;
            }
            else
            {
                var fileEntry = _fileCacheManager.GetFileCacheByHash(hash);
                if (fileEntry == null) continue;

                if (fileEntry.Size == null)
                {
                    fileEntry.Size = new FileInfo(fileEntry.ResolvedFilepath).Length;
                    _fileCacheManager.UpdateHashedFile(fileEntry, computeProperties: true);
                }

                fileSize = fileEntry.Size.Value;
                effectiveSize = fileSize;

                if (!skipDownscale)
                {
                    var preferredPath = _textureDownscaleService.GetPreferredPath(hash, fileEntry.ResolvedFilepath);
                    if (!string.IsNullOrEmpty(preferredPath) && File.Exists(preferredPath))
                    {
                        try
                        {
                            effectiveSize = new FileInfo(preferredPath).Length;
                        }
                        catch (Exception ex)
                        {
                            _logger.LogTrace(ex, "Failed to read size for preferred texture path {Path}", preferredPath);
                            effectiveSize = fileSize;
                        }
                    }
                    else
                    {
                        effectiveSize = fileSize;
                    }
                }
            }

            vramUsage += fileSize;
            effectiveVramUsage += effectiveSize;
        }

        pairHandler.LastAppliedApproximateVRAMBytes = vramUsage;
        pairHandler.LastAppliedApproximateEffectiveVRAMBytes = effectiveVramUsage;

        _logger.LogDebug("Calculated VRAM usage for {p}", pairHandler);

        // no warning of any kind on ignored pairs
        if (config.UIDsToIgnore
            .Exists(uid => string.Equals(uid, pairHandler.UserData.Alias, StringComparison.Ordinal) || string.Equals(uid, pairHandler.UserData.UID, StringComparison.Ordinal)))
            return true;

        bool isPrefPerm = pairHandler.HasStickyPermissions;

        // now check auto pause
        if (CheckForThreshold(config.AutoPausePlayersExceedingThresholds, config.VRAMSizeAutoPauseThresholdMiB * 1024L * 1024L,
            vramUsage, config.AutoPausePlayersWithPreferredPermissionsExceedingThresholds, isPrefPerm))
        {
            var message = $"Player {pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) exceeded your configured VRAM auto pause threshold and has been automatically paused\n" +
                $"{UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB}MiB";

            _mediator.Publish(new PerformanceNotificationMessage(
                $"{pairHandler.PlayerName} ({pairHandler.UserData.AliasOrUID}) automatically paused",
                message,
                pairHandler.UserData,
                true,
                pairHandler.PlayerName));

            _mediator.Publish(new PauseMessage(pairHandler.UserData));

            _mediator.Publish(new EventMessage(new Event(pairHandler.PlayerName, pairHandler.UserData, nameof(PlayerPerformanceService), EventSeverity.Warning,
                $"Exceeds VRAM threshold: automatically paused ({UiSharedService.ByteToString(vramUsage, addSuffix: true)}/{config.VRAMSizeAutoPauseThresholdMiB} MiB)")));

            return false;
        }

        return true;
    }

    private static bool CheckForThreshold(bool thresholdEnabled, long threshold, long value, bool checkForPrefPerm, bool isPrefPerm) =>
        thresholdEnabled && threshold > 0 && threshold < value && ((checkForPrefPerm && isPrefPerm) || !isPrefPerm);
}