using System.Net.Http.Headers;
using System.Text.Json.Serialization;
using Common.Configuration;
using Common.Configuration.DownloadClient;
using Domain.Models.Deluge.Exceptions;
using Domain.Models.Deluge.Request;
using Domain.Models.Deluge.Response;
using Infrastructure.Verticals.DownloadClient.Deluge.Extensions;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;

namespace Infrastructure.Verticals.DownloadClient.Deluge;

public sealed class DelugeClient
{
    private readonly DelugeConfig _config;
    private readonly HttpClient _httpClient;
    
    public DelugeClient(IOptions<DelugeConfig> config, IHttpClientFactory httpClientFactory)
    {
        _config = config.Value;
        _httpClient = httpClientFactory.CreateClient(nameof(DelugeService));
    }
    
    public async Task<bool> LoginAsync()
    {
        return await SendRequest<bool>("auth.login", _config.Password);
    }

    public async Task<bool> Logout()
    {
        return await SendRequest<bool>("auth.delete_session");
    }

    public async Task<List<DelugeTorrent>> ListTorrents(Dictionary<string, string>? filters = null)
    {
        filters ??= new Dictionary<string, string>();
        var keys = typeof(DelugeTorrent).GetAllJsonPropertyFromType();
        Dictionary<string, DelugeTorrent> result =
            await SendRequest<Dictionary<string, DelugeTorrent>>("core.get_torrents_status", filters, keys);
        return result.Values.ToList();
    }

    public async Task<List<DelugeTorrentExtended>> ListTorrentsExtended(Dictionary<string, string>? filters = null)
    {
        filters ??= new Dictionary<string, string>();
        var keys = typeof(DelugeTorrentExtended).GetAllJsonPropertyFromType();
        Dictionary<string, DelugeTorrentExtended> result =
            await SendRequest<Dictionary<string, DelugeTorrentExtended>>("core.get_torrents_status", filters, keys);
        return result.Values.ToList();
    }

    public async Task<DelugeTorrent?> GetTorrent(string hash)
    {
        List<DelugeTorrent> torrents = await ListTorrents(new Dictionary<string, string>() { { "hash", hash } });
        return torrents.FirstOrDefault();
    }

    public async Task<DelugeTorrentExtended?> GetTorrentExtended(string hash)
    {
        List<DelugeTorrentExtended> torrents =
            await ListTorrentsExtended(new Dictionary<string, string> { { "hash", hash } });
        return torrents.FirstOrDefault();
    }
    
    public async Task<TorrentStatus?> GetTorrentStatus(string hash)
    {
        return await SendRequest<TorrentStatus?>(
            "web.get_torrent_status",
            hash,
            new[] { "hash", "state", "name", "eta", "private", "total_done", "label", "seeding_time", "ratio" }
        );
    }
    
    public async Task<List<TorrentStatus>?> GetStatusForAllTorrents()
    {
        Dictionary<string, TorrentStatus>? downloads = await SendRequest<Dictionary<string, TorrentStatus>?>(
            "core.get_torrents_status",
            "",
            new[] { "hash", "state", "name", "eta", "private", "total_done", "label", "seeding_time", "ratio" }
        );
        
        return downloads?.Values.ToList();
    }

    public async Task<DelugeContents?> GetTorrentFiles(string hash)
    {
        return await SendRequest<DelugeContents?>("web.get_torrent_files", hash);
    }

    public async Task ChangeFilesPriority(string hash, List<int> priorities)
    {
        Dictionary<string, List<int>> filePriorities = new()
        {
            { "file_priorities", priorities }
        };

        await SendRequest<DelugeResponse<object>>("core.set_torrent_options", hash, filePriorities);
    }

    public async Task DeleteTorrents(List<string> hashes)
    {
        await SendRequest<DelugeResponse<object>>("core.remove_torrents", hashes, true);
    }

    private async Task<String> PostJson(String json)
    {
        StringContent content = new StringContent(json);
        content.Headers.ContentType = new MediaTypeWithQualityHeaderValue("application/json");

        var responseMessage = await _httpClient.PostAsync(new Uri(_config.Url, "/json"), content);
        responseMessage.EnsureSuccessStatusCode();

        var responseJson = await responseMessage.Content.ReadAsStringAsync();
        return responseJson;
    }

    private DelugeRequest CreateRequest(string method, params object[] parameters)
    {
        if (String.IsNullOrWhiteSpace(method))
        {
            throw new ArgumentException(nameof(method));
        }
        
        return new DelugeRequest(1, method, parameters);
    }
    
    public async Task<T> SendRequest<T>(string method, params object[] parameters)
    {
        return await SendRequest<T>(CreateRequest(method, parameters));
    }

    public async Task<T> SendRequest<T>(DelugeRequest webRequest)
    {
        var requestJson = JsonConvert.SerializeObject(webRequest, Formatting.None, new JsonSerializerSettings
        {
            NullValueHandling = webRequest.NullValueHandling
        });

        var responseJson = await PostJson(requestJson);
        var settings = new JsonSerializerSettings
        {
            Error = (_, args) =>
            {
                // Suppress the error and continue
                args.ErrorContext.Handled = true;
            }
        };
        
        DelugeResponse<T>? webResponse = JsonConvert.DeserializeObject<DelugeResponse<T>>(responseJson, settings);

        if (webResponse?.Error != null)
        {
            throw new DelugeClientException(webResponse.Error.Message);
        }

        if (webResponse?.ResponseId != webRequest.RequestId)
        {
            throw new DelugeClientException("desync");
        }

        return webResponse.Result;
    }
}