using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Nostr.Client.Json;
using Nostr.Client.Messages;
using Nostr.Client.Utils;

public enum LNURLErrorCode
{
    ServiceUnavailable = 1,
    InvalidLNURL = 2,
}

public class LNURLError : Exception
{
    public LNURLErrorCode Code { get; private set; }

    public LNURLError(LNURLErrorCode code, string message) : base(message)
    {
        Code = code;
    }
}

public class Lnurl
{
    private readonly HttpClient _client;

    public Lnurl(HttpClient client)
    {
        _client = client;
        _client.Timeout = TimeSpan.FromSeconds(60);
    }

    public static Uri? ParseLnUrl(string lnurl)
    {
        var emailRegex =
            new Regex(
                "^(([^<>()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$");

        lnurl = lnurl.ToLower().Trim();

        if (lnurl.StartsWith("lnurlp:"))
        {
            return new Uri(lnurl.Replace("lnurlp://", "https://"));
        }

        if (lnurl.StartsWith("lnurl"))
        {
            Bech32.Decode(lnurl, out var hrp, out var decoded);
            var decodedString = Encoding.UTF8.GetString(decoded!);
            if (!decodedString.StartsWith("http"))
            {
                return default;
            }

            return new Uri(decodedString);
        }

        if (emailRegex.IsMatch(lnurl))
        {
            var parts = lnurl.Split('@');
            return new Uri($"https://{parts[1]}/.well-known/lnurlp/{parts[0]}");
        }

        if (lnurl.StartsWith("https:"))
        {
            return new Uri(lnurl);
        }

        return default;
    }

    public async Task<LNURLService?> LoadAsync(string lnurl)
    {
        var url = ParseLnUrl(lnurl);
        var response = await _client.GetAsync(url);
        if (response.IsSuccessStatusCode)
        {
            var json = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<LNURLService>(json)!;
        }

        return default;
    }

    public async Task<LNURLInvoice> GetInvoiceAsync(LNURLService service, int amount, string? comment = null, NostrEvent? zap = null)
    {
        if (service == null || string.IsNullOrWhiteSpace(service.Callback))
        {
            throw new LNURLError(LNURLErrorCode.InvalidLNURL, "No callback url");
        }

        var query = new Dictionary<string, string>
        {
            ["amount"] = (amount * 1000).ToString()
        };

        if (!string.IsNullOrWhiteSpace(comment) && service.CommentAllowed.HasValue)
        {
            query["comment"] = comment;
        }

        if (!string.IsNullOrWhiteSpace(service.NostrPubkey) && zap != null)
        {
            query["nostr"] = JsonConvert.SerializeObject(zap, NostrSerializer.Settings);
        }

        var builder = new UriBuilder(service.Callback)
        {
            Query = string.Join('&', query.Select(a => $"{a.Key}={Uri.EscapeDataString(a.Value)}"))
        };

        try
        {
            var response = await _client.GetAsync(builder.Uri);
            if (response.IsSuccessStatusCode)
            {
                var json = await response.Content.ReadAsStringAsync();
                var data = JsonConvert.DeserializeObject<LNURLInvoice>(json);
                if (data?.Status == "ERROR")
                {
                    throw new Exception(data.Reason);
                }

                return data!;
            }

            throw new LNURLError(LNURLErrorCode.ServiceUnavailable, $"Failed to fetch invoice ({response.ReasonPhrase})");
        }
        catch (Exception e)
        {
            throw new LNURLError(LNURLErrorCode.ServiceUnavailable, $"Failed to load callback: {e.Message}");
        }
    }
}

public class LNURLService
{
    [JsonProperty("tag")]
    public string? Tag { get; init; }

    [JsonProperty("nostrPubkey")]
    public string? NostrPubkey { get; init; }

    [JsonProperty("minSendable")]
    public int? MinSendable { get; init; }

    [JsonProperty("maxSnedable")]
    public int? MaxSendable { get; init; }

    [JsonProperty("metadata")]
    public string Metadata { get; init; } = null!;

    [JsonProperty("callback")]
    public string Callback { get; init; } = null!;

    [JsonProperty("commentAllowed")]
    public int? CommentAllowed { get; init; }
}

public class LNURLStatus
{
    [JsonProperty("status")]
    public string Status { get; set; } = null!;

    [JsonProperty("reason")]
    public string? Reason { get; set; }
}

public class LNURLInvoice : LNURLStatus
{
    [JsonProperty("pr")]
    public string Pr { get; set; } = null!;

    [JsonProperty("successAction")]
    public LNURLSuccessAction? SuccessAction { get; set; }
}

public class LNURLSuccessAction
{
    [JsonProperty("description")]
    public string? Description { get; set; }

    [JsonProperty("url")]
    public string? Url { get; set; }
}
