using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using KfChatDotNetBot.Models;
using KfChatDotNetBot.Models.DbModels;
using KfChatDotNetBot.Settings;
using Microsoft.EntityFrameworkCore;
using NLog;

namespace KfChatDotNetBot.Services;

public class DLive(ChatBot kfChatBot) : IDisposable
{
    private readonly Logger _logger = LogManager.GetCurrentClassLogger();
    private Task? _liveStatusCheckTask;
    private CancellationTokenSource _liveStatusCheckTaskCts = new();

    public void StartLiveStatusCheck()
    {
        _liveStatusCheckTaskCts = new CancellationTokenSource();
        _liveStatusCheckTask = Task.Run(LiveStatusCheckTask, _liveStatusCheckTaskCts.Token);
    }
    
    private async Task LiveStatusCheckTask()
    {
        var interval = (await SettingsProvider.GetValueAsync(BuiltIn.Keys.DLiveCheckInterval)).ToType<int>();
        using var timer = new PeriodicTimer(TimeSpan.FromSeconds(interval));
        while (await timer.WaitForNextTickAsync(_liveStatusCheckTaskCts.Token))
        {
            var ct = _liveStatusCheckTaskCts.Token;
            _logger.Debug("Going to check if anyone is live on DLive now");
            await using var db = new ApplicationDbContext();
            var streams = await db.Streams.Where(s => s.Service == StreamService.DLive).Include(s => s.User).ToListAsync(ct);
            var settings = await SettingsProvider.GetMultipleValuesAsync([
                BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams, BuiltIn.Keys.CaptureEnabled,
                BuiltIn.Keys.CaptureLockTable
            ]);
            var currentlyLive = settings[BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams].JsonDeserialize<List<string>>() ?? [];
            var locks = settings[BuiltIn.Keys.CaptureLockTable].JsonDeserialize<Dictionary<string, string>>() ?? new Dictionary<string, string>();
            foreach (var stream in streams)
            {
                var username = stream.StreamUrl.Split('/').LastOrDefault();
                if (username == null)
                {
                    _logger.Error($"Could not determine the DLive username from {stream.StreamUrl} in row {stream.Id}");
                    continue;
                }

                if (locks.ContainsKey(stream.StreamUrl))
                {
                    if (File.Exists(locks[stream.StreamUrl]))
                    {
                        _logger.Debug($"{stream.StreamUrl} has an existing lock at {locks[stream.StreamUrl]}, ignoring");
                        continue;
                    }
                }

                DLiveIsLiveModel status;
                try
                {
                    status = await IsLive(username, ct);
                }
                catch (Exception e)
                {
                    _logger.Error($"Caught an exception while retrieving live status for {username}");
                    _logger.Error(e);
                    continue;
                }
                if (!status.IsLive)
                {
                    currentlyLive.Remove(username);
                    continue;
                }
                // Already known to be live so do nothing
                if (currentlyLive.Contains(username)) continue;
                
                var identity = "A streamer";
                if (stream.User != null)
                {
                    identity = "@" + stream.User.KfUsername;
                }

                await kfChatBot.SendChatMessageAsync($"{identity} is live! {status.Title} {stream.StreamUrl}", true);

                BaseMetaModel? meta = null;
                if (stream.Metadata != null)
                {
                    try
                    {
                        meta = JsonSerializer.Deserialize<BaseMetaModel>(stream.Metadata);
                    }
                    catch (Exception e)
                    {
                        _logger.Error($"Caught an exception when attempting to deserialize metadata for DLive stream {stream.StreamUrl}");
                        _logger.Error(e);
                    }
                }

                if (stream.AutoCapture && settings[BuiltIn.Keys.CaptureEnabled].ToBoolean())
                {
                    _logger.Info($"{stream.StreamUrl} is live and set to auto capture");
                    _ = new StreamCapture(stream.StreamUrl, StreamCaptureMethods.Streamlink, meta?.CaptureOverrides, ct).CaptureAsync();
                }
                currentlyLive.Add(username);
            }
            
            _logger.Debug($"Persisting currently live streams, count is {currentlyLive.Count}");
            await SettingsProvider.SetValueAsJsonObjectAsync(BuiltIn.Keys.DLivePersistedCurrentlyLiveStreams,
                currentlyLive);
        }
    }
    
    public static async Task<DLiveIsLiveModel> IsLive(string username, CancellationToken ct = default)
    {
        var logger = LogManager.GetCurrentClassLogger();
        var proxy = await SettingsProvider.GetValueAsync(BuiltIn.Keys.Proxy);
        var handler = new HttpClientHandler
        {
            AutomaticDecompression = DecompressionMethods.All,

        };
        if (proxy.Value != null)
        {
            handler.Proxy = new WebProxy(proxy.Value);
            handler.UseProxy = true;
            logger.Debug($"Set proxy for the DLive GraphQL request to {proxy.Value}");
        }

        var gql = "query { userByDisplayName(displayname:\"" + username + "\") { livestream " +
                  "{ content createdAt title thumbnailUrl watchingCount } username } }";
        logger.Debug($"Built GraphQL query string: {gql}");
        var jsonBody = new Dictionary<string, object>
        {
            { "query", gql }
        };
        logger.Debug("Created dictionary object for the JSON payload, should serialize to following:");
        logger.Debug(JsonSerializer.Serialize(jsonBody));
        using var client = new HttpClient(handler);
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var postBody = JsonContent.Create(jsonBody);
        var response = await client.PostAsync("https://graphigo.prd.dlive.tv/", postBody, ct);
        var content = await response.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: ct);
        logger.Debug("DLive GraphQL endpoint returned the following JSON");
        logger.Debug(content.GetRawText);
        // Not live
        // {"data":{"userByDisplayName":{"livestream":null,"username":"planesfan"}}}
        // Live
        // {
        //     "data": {
        //         "userByDisplayName": {
        //             "livestream": {
        //                 "content": "",
        //                 "createdAt": "1752699050000",
        //                 "title": "HUNT FULL ACHAT VENEZ DONNER VOS CALL!!!",
        //                 "thumbnailUrl": "https://images.prd.dlivecdn.com/live-thumbnail/a587c2a8-6288-11f0-90fc-d638708e4bb8",
        //                 "watchingCount": 799
        //             },
        //             "username": "cashpistache1"
        //         }
        //     }
        // }
        try
        {
            var responseData = content.GetProperty("data").GetProperty("userByDisplayName");
            if (responseData.ValueKind == JsonValueKind.Null)
            {
                logger.Error($"userByDisplayName was null for {username}. Just going to throw an exception because who knows what's going on");
                logger.Error(content.GetRawText);
                throw new NullReferenceException($"userByDisplayName for {username}");
            }
            var isLive = responseData.GetProperty("livestream").ValueKind == JsonValueKind.Object;
            string? title = null;
            if (isLive)
            {
                title = responseData.GetProperty("livestream").GetProperty("title").GetString();
            }

            return new DLiveIsLiveModel
            {
                IsLive = isLive,
                Title = title,
                Username = responseData.GetProperty("username").GetString() ?? "username was null in GraphQL response"
            };
        }
        catch (Exception e)
        {
            logger.Error($"Bot shit itself while trying to check if {username} is live. JSON payload follows");
            logger.Error(content.GetRawText);
            logger.Error(e);
            throw;
        }
    }

    public void Dispose()
    {
        _liveStatusCheckTaskCts.Cancel();
        _liveStatusCheckTask?.Dispose();
        GC.SuppressFinalize(this);
    }
}