using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Threading;
using System.Threading.Tasks;
using Luski.net.Enums;
using Luski.Shared.PublicServers.V1.Enums;
using Luski.Shared.PublicServers.V1.ServerToClient.HTTP;

namespace Luski.net;

public class API
{
    [JsonIgnore]
    public MainServer MainServer { get; internal set; }

    public bool IsAnyServerLoggedin { get; internal set; }

    public const string DefaultVersion = "v1";

    internal List<string> Versions = new()
    {
        DefaultVersion
    };

    public IReadOnlyList<string> SupportedVersions => Versions.AsReadOnly();
    internal List<PublicServer> InternalServers { get; } = new();
    public IReadOnlyList<PublicServer> LoadedServers => InternalServers.AsReadOnly();
    
    internal List<PublicServer> InternalFailedServers { get; } = new();
    public IReadOnlyList<PublicServer> FailedServers => InternalFailedServers.AsReadOnly();
    
    private static HttpResponseMessage GetFromServer(string Domain, string ApiVersion, bool Secure, string Path, CancellationToken CancellationToken, params KeyValuePair<string, string?>[] Headers)
    {
        using HttpClient web = new();
        web.Timeout = TimeSpan.FromSeconds(10);
        if (Headers is not null && Headers.Length > 0) foreach (KeyValuePair<string, string?> header in Headers) web.DefaultRequestHeaders.Add(header.Key, header.Value);
        return web.GetAsync($"{(Secure ? "https" : "http" )}://{Domain}/{ApiVersion}/{Path}", cancellationToken: CancellationToken).Result;
    }
    private static Task<Tresult> GetFromServer<Tresult>(string Domain, string ApiVersion, bool Secure, string Path, JsonTypeInfo<Tresult> Type, CancellationToken CancellationToken, params KeyValuePair<string, string?>[] Headers) where Tresult : STC, new()
    {
        HttpResponseMessage ServerResponce = GetFromServer(Domain, ApiVersion, Secure, Path, CancellationToken, Headers);
        Tresult temp = new();
        string raw = ServerResponce.Content.ReadAsStringAsync(CancellationToken).Result;
        try
        {
            temp = JsonSerializer.Deserialize(raw, Type)!;
        }
        catch (Exception e)
        {
            Console.WriteLine("JSON parse failed for the following data as type {0}\n{1}", temp.GetType(), raw);
        }
        if (temp is null) return Task.FromResult(new Tresult() { StatusCode = ServerResponce.StatusCode, Error = ErrorCode.ServerError, ErrorMessage = $"Server responded with empty data" });
        return Task.FromResult(temp);
    }
    
    public Task<ServerInfoSTC> GetServerInfo(string Domain, string Version = DefaultVersion,
        bool Secure = true)
    {
        ServerInfoSTC? si = GetFromServer(Domain, Version, Secure, "socketserver", ServerInfoSTCContext.Default.ServerInfoSTC, CancellationToken.None).Result;
        if (si is null) throw new Exception("Bad Response");
        return Task.FromResult(si);
    }

    public Task<bool> TryGetServerInfo([NotNullWhen(true)]out ServerInfoSTC? si, string Domain, string Version = DefaultVersion,
        bool Secure = true)
    {
        
        try
        {
            si = GetServerInfo(Domain, Version, Secure).Result;
            return Task.FromResult(true); 
        }
        catch (Exception e)
        {
            si = null;
            return Task.FromResult(false);
        }
    }

    public Task<bool> TryGetPublicServer(out PublicServer Server, string Domain, string Version = DefaultVersion,
        bool Secure = true, bool GenerateEncryption = true, bool LogConsole = false)
    {
        try
        {
            Task<PublicServer> result = GetPublicServer(Domain, Version, Secure, GenerateEncryption, LogConsole);
            Task.WaitAll(result);
            Server = result.Result;
            return Task.FromResult(true);
        }
        catch (Exception e)
        {
            if (!e.Message.Contains("Connection refused")) Console.WriteLine(e);
            Server = null!;
            return Task.FromResult(false);
        }
    }
    
    
    public async Task<PublicServer> GetPublicServer(string Domain, string Version = DefaultVersion, bool Secure = true, bool GenerateEncryption = true, bool LogConsole = false)
    {
        PublicServer s;
        try
        {
            IEnumerable<PublicServer> isl = InternalServers.Where(a => (a.Domain == Domain && a.ApiVersion == Version));
            if (isl.Any()) return isl.First();
            s = await PublicServer.GetServer(InternalFailedServers, Domain, Version, Secure, GenerateEncryption, LogConsole);
        }
        catch (Exception e)
        {
            Console.WriteLine("Failed to connect to public server '{0}' using API {1}. No alternate server was found.", Domain, Version);
            throw;
        }
        
        string? f = s.Storage.GetStorageDirectory(StorageDirectory.StorageInfo) + "token";
        if (File.Exists(f))
        {
            f = File.ReadAllText(f);
        }
        else f = null;

        try
        {
            if (f is not null)
            {
                bool b  = await s.LoginViaToken(f);
                if (b)
                {
                    IsAnyServerLoggedin = true;
                }
                else
                {
                    s.Token = null;
                    s.Error = null;
                }
            }
        }
        catch
        {
        }

        InternalServers.Add(s);
        return s;
    }
    
    public MainServer GetMainServer(string Domain, string Version = DefaultVersion)
    {
        DateTime dt = DateTime.UtcNow;
        Console.WriteLine("Conecting to main server '{0}' using API {1}.", Domain, Version);
        MainServer = new(Domain, Version)
        {
            ServerType = ServerType.Main
        };
        Console.WriteLine("Connected to main server '{0}' using API {1} in {2}.", Domain, Version, DateTime.UtcNow.Subtract(dt).ToString("g"));
        return MainServer;
    }
}