using System.Net;
using System.Text.Json;
using HtmlAgilityPack;
using KfChatDotNetBot.Settings;
using NLog;

namespace KfChatDotNetBot.Services;

public class KfTokenService
{
    private readonly Logger _logger = LogManager.GetCurrentClassLogger();
    private readonly CancellationToken _ctx;
    private CookieContainer _cookies = new();
    private KiwiFlare _kiwiFlare;
    private readonly string _kfDomain;
    private readonly string? _proxy;

    public KfTokenService(string kfDomain, string? proxy = null, CancellationToken? cancellationToken = null)
    {
        _ctx = cancellationToken ?? CancellationToken.None;
        _kiwiFlare = new KiwiFlare(kfDomain, proxy, cancellationToken);
        _proxy = proxy;
        _kfDomain = kfDomain;
        var cachedCookies = SettingsProvider.GetValueAsync(BuiltIn.Keys.KiwiFarmsCookies).Result
            .JsonDeserialize<Dictionary<string, string>>();
        // This shouldn't happen as the setting's default value is {}, but I'm just doing it to shut the IDE up
        if (cachedCookies == null) return;
        foreach (var key in cachedCookies.Keys)
        {
            _cookies.Add(new Cookie(key, cachedCookies[key], "/", _kfDomain));
        }
    }
    
    private HttpClientHandler GetHttpClientHandler()
    {
        var handler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All };
        if (_proxy != null)
        {
            handler.Proxy = new WebProxy(_proxy);
            handler.UseProxy = true;
        }
        handler.CookieContainer = _cookies;
        return handler;
    }

    private async Task GetNewClearanceToken()
    {
        _logger.Debug("Getting a new clearance token");
        var i = 0;
        // Shitty retry logic as the forum is still annoyingly unstable
        while (i < 10)
        {
            i++;
            try
            {
                var challenge = await _kiwiFlare.GetChallenge();
                if (challenge == null)
                {
                    _logger.Error("Challenge data was null. Might be KiwiFlare is only partially enabled? Not going to do anything.");
                    return;
                }
                var solution = await _kiwiFlare.SolveChallenge(challenge);
                string token;
                if (challenge.IsTtrs)
                {
                    token = await _kiwiFlare.SubmitAnswerTtrs(solution);
                    _cookies.Add(new Cookie("ttrs_clearance", token, "/", _kfDomain));

                }
                else
                {
                    token = await _kiwiFlare.SubmitAnswer(solution);
                    _cookies.Add(new Cookie("sssg_clearance", token, "/", _kfDomain));
                }
                _logger.Debug("Successfully retrieved a new token and added to the cookie container");
                return;
            }
            catch (Exception e)
            {
                _logger.Error($"Failed to solve the KiwiFlare challenge, attempt {i} of 10");
                _logger.Error(e);
            }
        }
        _logger.Error("Ran out of attempts");
        throw new Exception("Failed to solve the challenge");
    }

    private async Task<string> GetLoginPage()
    {
        using var client = new HttpClient(GetHttpClientHandler());
        var response = await client.GetAsync($"https://{_kfDomain}/login", _ctx);
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync(_ctx);
        if (response.StatusCode == HttpStatusCode.NonAuthoritativeInformation)
        {
            _logger.Info("Caught a 203 response when trying to load the logon page which means we have to solve a KiwiFlare challenge");
            await GetNewClearanceToken();
            _logger.Info("Solved the challenge, now going to grab the login page again");
            response = await client.GetAsync($"https://{_kfDomain}/login", _ctx);
            content = await response.Content.ReadAsStringAsync(_ctx);
        }
        return content;
    }

    public async Task<bool> IsLoggedIn()
    {
        var document = new HtmlDocument();
        document.LoadHtml(await GetLoginPage());
        var html = document.DocumentNode.SelectSingleNode("//html");
        if (html == null) throw new Exception("Caught a null when retrieving html element");
        if (!html.Attributes.Contains("data-logged-in"))
        {
            throw new Exception("data-logged-in attribute missing");
        }

        var success = html.Attributes["data-logged-in"].Value == "true";
        if (success)
        {
            await SaveCookies();
        }

        return success;
    }

    public async Task PerformLogin(string username, string password)
    {
        var document = new HtmlDocument();
        document.LoadHtml(await GetLoginPage());
        var html = document.DocumentNode.SelectSingleNode("//html");
        if (html == null) throw new Exception("Caught a null when retrieving html element");
        // Already logged in
        if (html.GetAttributeValue("data-logged-in", "false") == "true") return;
        if (!html.Attributes.Contains("data-csrf")) throw new Exception("data-csrf missing from html element");
        var csrf = html.GetAttributeValue("data-csrf", string.Empty);
        var formData = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>
        {
            new("_xfToken", csrf),
            new("login", username),
            new("password", password),
            new("_xfRedirect", $"https://{_kfDomain}/"),
            new("remember", "1")
        });
        using var client = new HttpClient(GetHttpClientHandler());
        var postResponse = await client.PostAsync($"https://{_kfDomain}/login/login", formData, _ctx);
        if (postResponse.StatusCode == HttpStatusCode.SeeOther)
        {
            _logger.Debug("Got HTTP response 303. Success!");
            return;
        }
        postResponse.EnsureSuccessStatusCode();
        _logger.Info($"Received HTTP response {postResponse.StatusCode}, checking to see if we're logged in");
        var postDocument = new HtmlDocument();
        postDocument.Load(await postResponse.Content.ReadAsStreamAsync(_ctx));
        html = postDocument.DocumentNode.SelectSingleNode("//html");
        if (html == null) throw new Exception("Caught a null when retrieving html element");
        // Logged in!
        if (html.GetAttributeValue("data-logged-in", "false") == "true") return;
        _logger.Error("Not logged in :(");
        var message =
            postDocument.DocumentNode.SelectSingleNode(
                "//div[@class=\"blockMessage blockMessage--error blockMessage--iconic\"]");
        _logger.Error($"Error from the page was {message?.InnerText}");
        throw new KiwiFarmsLogonFailedException();
    }

    public string? GetXfSessionCookie()
    {
        _logger.Debug("JSON serialization of all the cookies");
        _logger.Debug(JsonSerializer.Serialize(_cookies.GetAllCookies()));
        var cookie = _cookies.GetAllCookies()["xf_session"];
        _logger.Debug($"xf_session => {cookie?.Value}");
        return cookie?.Value;
    }

    public Dictionary<string, string> GetCookies()
    {
        return _cookies.GetAllCookies().ToDictionary(cookie => cookie.Name, cookie => cookie.Value);
    }

    public async Task SaveCookies()
    {
        _logger.Debug("Saving cookies");
        var cookiesToSave = _cookies.GetAllCookies().ToDictionary(cookie => cookie.Name, cookie => cookie.Value);
        await SettingsProvider.SetValueAsJsonObjectAsync(BuiltIn.Keys.KiwiFarmsCookies, cookiesToSave);
    }

    public void WipeCookies()
    {
        _logger.Info("Wiping out cookies");
        _cookies = new CookieContainer();
    }
    
    public class KiwiFarmsLogonFailedException : Exception;
}