using System.Net.WebSockets;
using Newtonsoft.Json;
using Nostr.Client.Identifiers;
using Nostr.Client.Json;
using Nostr.Client.Messages;
using Nostr.Client.Utils;
using NostrRelay;
using NostrServices.Client;
using NostrServices.Services;
using ProtoBuf;
using StackExchange.Redis;

namespace NostrServices;

public static class Extensions
{
    public static string? GetFirstTagValue(this CompactEvent ev, string key)
    {
        return ev.Tags.FirstOrDefault(a => a.Key == key)?.Values[0];
    }

    public static async Task<bool> SetAsync<T>(this IDatabase db, RedisKey key, T val, TimeSpan? expire = null)
    {
        using var ms = new MemoryStream();
        Serializer.Serialize(ms, val);
        return await db.StringSetAsync(key, ms.ToArray(), expire);
    }

    public static async Task<T?> GetAsync<T>(this IDatabase db, RedisKey key) where T : class
    {
        var data = await db.StringGetAsync(key);
        if (data is {HasValue: true, IsNullOrEmpty: false})
        {
            return Serializer.Deserialize<T>(((byte[])data!).AsSpan());
        }

        return default;
    }

    public static NostrIdentifier ToIdentifier(this NostrEvent ev)
    {
        if ((long)ev.Kind is >= 30_000 and < 40_000)
        {
            return new NostrAddressIdentifier(ev.Tags!.FindFirstTagValue("d")!, ev.Pubkey!, [], ev.Kind);
        }

        return new NostrEventIdentifier(ev.Id!, ev.Pubkey, [], ev.Kind);
    }

    public static NostrIdentifier ToIdentifier(this CompactEvent ev)
    {
        if (ev.Kind is >= 30_000 and < 40_000)
        {
            return new NostrAddressIdentifier(ev.GetFirstTagValue("d")!, ev.PubKey.ToHex(), [], (NostrKind)ev.Kind);
        }

        return new NostrEventIdentifier(ev.Id.ToHex(), ev.PubKey.ToHex(), [], (NostrKind)ev.Kind);
    }

    public static NostrIdentifier ToIdentifier(this CompactProfile px)
    {
        return new NostrProfileIdentifier(px.PubKey.ToHex(), []);
    }

    public static async Task<NostrIdentifier?> TryParseIdentifier(string id)
    {
        NostrIdentifier? nout;
        if ((NostrIdentifierParser.TryParse(id, out nout) && nout != default) ||
            (nout = NostrBareIdentifier.Parse(id)) != default ||
            (nout = await TryParseNip05(id, default)) != default)
        {
            return nout;
        }

        return default;
    }

    private static async Task<NostrIdentifier?> TryParseNip05(string id, CancellationToken cts)
    {
        try
        {
            using var client = new HttpClient();
            if (id.Contains("@"))
            {
                var idSplit = id.Split("@");
                var url = new Uri($"https://{idSplit[1]}/.well-known/nostr.json?name={Uri.EscapeDataString(idSplit[0])}");
                var json = await client.GetStringAsync(url, cts);
                var parsed = JsonConvert.DeserializeObject<NostrJson>(json);
                var match = parsed?.Names?.FirstOrDefault(a => a.Key.Equals(idSplit[0], StringComparison.CurrentCultureIgnoreCase));
                if (match.HasValue && !string.IsNullOrEmpty(match.Value.Value))
                {
                    return new NostrProfileIdentifier(match.Value.Value.ToLower(), null);
                }
            }
        }
        catch (Exception ex)
        {
            //_logger.LogWarning("Failed to parse nostr address {id} {msg}", id, ex.Message);
        }

        return default;
    }


    class NostrJson
    {
        [JsonProperty("names")]
        public Dictionary<string, string>? Names { get; init; } = new();
    }
}
