using System.Net;
using System.Text;
using System.Text.Json;
using Baya.Infrastructure.CrossCutting.Seams;
using Baya.Infrastructure.CrossCutting.Seams.Real;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Baya.Test.Foundation.Seams;

/// <summary>
/// The Development-only Telegram OTP relay adapter: it must speak the relay's contract exactly (routes, body
/// keys, <c>X-Api-Key</c>), treat an undelivered message as a failure rather than a silent success, and never
/// put the OTP code in a log line.
/// </summary>
public class TelegramSmsSenderTests
{
    private const string ApiKey = "0123456789abcdef0123456789abcdef";

    [Fact]
    public async Task SendOtpAsync_PostsTheCodeToSendOtpWithTheApiKeyHeader()
    {
        var (sender, handler, _) = Build();

        await sender.SendOtpAsync("09120000001", "135790");

        var request = Assert.Single(handler.Requests);
        Assert.Equal(HttpMethod.Post, request.Method);
        Assert.Equal("http://127.0.0.1:5010/send_otp", request.Url);
        Assert.Equal(ApiKey, request.ApiKey);

        using var body = JsonDocument.Parse(request.Body);
        Assert.Equal("09120000001", body.RootElement.GetProperty("phone").GetString());
        Assert.Equal("135790", body.RootElement.GetProperty("code").GetString());
    }

    [Fact]
    public async Task SendAsync_PostsTheMessageToSend()
    {
        var (sender, handler, _) = Build();

        await sender.SendAsync("09120000001", "your booking is confirmed");

        var request = Assert.Single(handler.Requests);
        Assert.Equal("http://127.0.0.1:5010/send", request.Url);
        Assert.Equal(ApiKey, request.ApiKey);

        using var body = JsonDocument.Parse(request.Body);
        Assert.Equal("your booking is confirmed", body.RootElement.GetProperty("message").GetString());
    }

    [Fact]
    public async Task NoRecipientReceivedIt_Throws()
    {
        // 502 = the relay reached nobody. Swallowing it would report a login code that was never delivered.
        var (sender, _, _) = Build(HttpStatusCode.BadGateway, """{"ok":false,"delivered":[],"failed":[]}""");

        await Assert.ThrowsAsync<InvalidOperationException>(() => sender.SendOtpAsync("09120000001", "135790"));
    }

    [Fact]
    public async Task SuccessStatusWithNotOkBody_Throws()
    {
        var (sender, _, _) = Build(HttpStatusCode.OK, """{"ok":false}""");

        await Assert.ThrowsAsync<InvalidOperationException>(() => sender.SendOtpAsync("09120000001", "135790"));
    }

    [Fact]
    public async Task UnconfiguredApiKey_ThrowsNamingTheConfigKey()
    {
        var (sender, handler, _) = Build(apiKey: "ab8984974bc1fe5ce514d0fd74f71c8738b3aed92a7e4d86");

        var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => sender.SendOtpAsync("09120000001", "135790"));

        Assert.Contains("Seams:Sms:Telegram:ApiKey", ex.Message);
        Assert.Empty(handler.Requests); // never sent unauthenticated
    }

    [Fact]
    public async Task TheOtpCodeNeverReachesTheLog()
    {
        var (ok, _, okLog) = Build();
        await ok.SendOtpAsync("09120000001", "135790");

        var (failing, _, failLog) = Build(HttpStatusCode.BadGateway, """{"ok":false}""");
        await Assert.ThrowsAsync<InvalidOperationException>(() => failing.SendOtpAsync("09120000001", "135790"));

        Assert.NotEmpty(okLog.Lines);
        Assert.NotEmpty(failLog.Lines);
        Assert.DoesNotContain(okLog.Lines.Concat(failLog.Lines), line => line.Contains("135790"));
    }

    private static (TelegramSmsSender Sender, StubHandler Handler, CapturingLogger Logger) Build(
        HttpStatusCode status = HttpStatusCode.OK,
        string body = """{"ok":true,"delivered":["11111111"],"failed":[]}""",
        string apiKey = ApiKey)
    {
        var handler = new StubHandler(status, body);
        var client = new HttpClient(handler) { BaseAddress = new Uri("http://127.0.0.1:5010/") };
        var options = Options.Create(new SeamOptions
        {
            Sms = new SmsOptions
            {
                Provider = SeamProviders.Telegram,
                Telegram = new TelegramOptions { BaseUrl = "http://127.0.0.1:5010", ApiKey = apiKey },
            },
        });
        var logger = new CapturingLogger();

        return (new TelegramSmsSender(client, options, logger), handler, logger);
    }

    private sealed record CapturedRequest(HttpMethod Method, string Url, string? ApiKey, string Body);

    /// <summary>Answers every call from memory — the adapter is exercised with no network at all.</summary>
    private sealed class StubHandler(HttpStatusCode status, string body) : HttpMessageHandler
    {
        public List<CapturedRequest> Requests { get; } = [];

        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
        {
            Requests.Add(new CapturedRequest(
                request.Method,
                request.RequestUri!.ToString(),
                request.Headers.TryGetValues("X-Api-Key", out var values) ? values.FirstOrDefault() : null,
                request.Content is null ? string.Empty : await request.Content.ReadAsStringAsync(cancellationToken)));

            return new HttpResponseMessage(status)
            {
                Content = new StringContent(body, Encoding.UTF8, "application/json"),
            };
        }
    }

    private sealed class CapturingLogger : ILogger<TelegramSmsSender>
    {
        public List<string> Lines { get; } = [];

        public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;

        public bool IsEnabled(LogLevel logLevel) => true;

        public void Log<TState>(
            LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
            => Lines.Add(formatter(state, exception));
    }
}
