// Webhook alıcısı (.NET 8, HttpListener; bağımlılıksız): "dürt ve çek" deseni.
//
// İmzayı (Standard Webhooks) ve zaman damgasını (±5 dk) doğrular, webhook-id'yi 24 saat saklar, hemen 202 döner;
// içeriği akıştan çekmeyi bağlayıcıya bırakır. Alıcı denetiminin dört isteğini geçer: doğru imza 202, bozuk imza 401,
// eski zaman damgası 401, aynı webhook-id 200.
//
// Kullanım: set YHA_WEBHOOK_SIRRI=whsec_...  &&  dotnet run            (http://localhost:8080/ dinler)
//           dotnet run -- --oz-sinama                                   (imza ve dört isteği sınar, çıkış kodu 0)
// Ajans yalnızca https adrese gönderir: önüne TLS sonlandıran bir vekil koyun.
using System.Collections.Concurrent;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json.Nodes;

namespace YhaOrnek;

public static class WebhookAlici
{
    private const int Tolerans = 300;
    private static readonly ConcurrentDictionary<string, DateTime> Gorulen = new();

    public static bool ImzaGecerli(IEnumerable<string> sirlar, string? wid, string? zaman, string? imzalar, byte[] govde, long? simdi = null)
    {
        if (string.IsNullOrEmpty(wid) || !long.TryParse(zaman, out var z) || string.IsNullOrEmpty(imzalar))
        {
            return false;
        }
        if (Math.Abs((simdi ?? DateTimeOffset.UtcNow.ToUnixTimeSeconds()) - z) > Tolerans)
        {
            return false;
        }
        foreach (var sir in sirlar)
        {
            using var hmac = new HMACSHA256(Convert.FromBase64String(sir.Replace("whsec_", "")));
            var beklenen = hmac.ComputeHash([.. Encoding.UTF8.GetBytes($"{wid}.{zaman}."), .. govde]);
            foreach (var p in imzalar.Split(' '))
            {
                if (!p.StartsWith("v1,"))
                {
                    continue;
                }
                try
                {
                    if (CryptographicOperations.FixedTimeEquals(Convert.FromBase64String(p[3..]), beklenen))
                    {
                        return true;
                    }
                }
                catch (FormatException)
                {
                    // bozuk base64: geçersiz imza
                }
            }
        }
        return false;
    }

    /// <summary>Tek isteği değerlendirir: (durum kodu, gövde, dürtülsün mü).</summary>
    public static (int, string, bool) Degerlendir(string[] sirlar, string? wid, string? zaman, string? imza, byte[] govde)
    {
        if (govde.Length > 65536)
        {
            return (413, "{\"hata\":\"govde_cok_buyuk\"}", false);
        }
        if (!ImzaGecerli(sirlar, wid, zaman, imza, govde))
        {
            return (401, "{\"hata\":\"imza_gecersiz\"}", false);
        }
        foreach (var (k, t) in Gorulen)
        {
            if (DateTime.UtcNow - t > TimeSpan.FromHours(24))
            {
                Gorulen.TryRemove(k, out _);
            }
        }
        if (!Gorulen.TryAdd(wid!, DateTime.UtcNow))
        {
            return (200, "{\"tekrar\":true}", false); // aynı bildirim: sessizce kabul
        }
        var g = JsonNode.Parse(govde);
        var tur = g?["olay_turu"]?.GetValue<string>();
        if (tur == "sinama.ping")
        {
            return (200, "{\"alici\":\"yha-ornek-webhook-alici\",\"surum\":\"1.0.0\"}", false);
        }
        var durt = tur != "sinama.denetim" && g?["deneme"]?.GetValue<bool>() != true;
        return (202, "{\"alindi\":true}", durt);
    }

    private static int OzSinama()
    {
        const string sir = "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw";
        var vektor = ImzaGecerli([sir], "msg_p5jXN8AQM9LWM0D4loKWxJek", "1614265330", "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=",
            Encoding.UTF8.GetBytes("{\"test\": 2432232314}"), 1614265330);
        string Imzala(string wid, long z, byte[] g)
        {
            using var h = new HMACSHA256(Convert.FromBase64String(sir[6..]));
            return "v1," + Convert.ToBase64String(h.ComputeHash([.. Encoding.UTF8.GetBytes($"{wid}.{z}."), .. g]));
        }
        var govde = Encoding.UTF8.GetBytes("{\"olay_turu\":\"sinama.denetim\",\"deneme\":true}");
        var simdi = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
        var dogru = Imzala("m1", simdi, govde);
        var bozuk = dogru[..^2] + (dogru[^2] == 'A' ? 'B' : 'A') + dogru[^1];
        var sonuclar = new[]
        {
            vektor,
            Degerlendir([sir], "m1", simdi.ToString(), dogru, govde).Item1 < 300,
            Degerlendir([sir], "m2", simdi.ToString(), bozuk, govde).Item1 == 401,
            Degerlendir([sir], "m3", (simdi - 600).ToString(), Imzala("m3", simdi - 600, govde), govde).Item1 == 401,
            Degerlendir([sir], "m1", simdi.ToString(), dogru, govde).Item1 == 200,
        };
        Console.WriteLine(string.Join(" ", sonuclar.Select((s, i) => $"{i}:{(s ? "GEÇTİ" : "KALDI")}")));
        return sonuclar.All(s => s) ? 0 : 1;
    }

    public static async Task<int> Main(string[] arg)
    {
        if (arg.Contains("--oz-sinama"))
        {
            return OzSinama();
        }
        var sirlar = Environment.GetEnvironmentVariable("YHA_WEBHOOK_SIRRI")!.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        using var dinleyici = new HttpListener();
        dinleyici.Prefixes.Add($"http://localhost:{Environment.GetEnvironmentVariable("PORT") ?? "8080"}/");
        dinleyici.Start();
        while (true)
        {
            var c = await dinleyici.GetContextAsync();
            using var bellek = new MemoryStream();
            await c.Request.InputStream.CopyToAsync(bellek);
            var (durum, govde, durt) = c.Request.HttpMethod == "POST"
                ? Degerlendir(sirlar, c.Request.Headers["webhook-id"], c.Request.Headers["webhook-timestamp"], c.Request.Headers["webhook-signature"], bellek.ToArray())
                : (405, "{\"hata\":\"yontem\"}", false);
            c.Response.StatusCode = durum;
            c.Response.ContentType = "application/json";
            await c.Response.OutputStream.WriteAsync(Encoding.UTF8.GetBytes(govde));
            c.Response.Close();
            if (durt)
            {
                Console.WriteLine("dürtü alındı: bağlayıcıyı çalıştırın (../baglayici, dotnet run -- --tek-sefer)");
            }
        }
    }
}
