using GerbilManagerWebAPI.Inbox;
using GerbilManagerWebAPI.Models;
using MailKit.Security;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;

namespace GerbilManager.Tests;

/// <summary>INBOX-3: threaded reply send — correct In-Reply-To/References/Subject, status
/// transition to Answered, and the not-configured / auth-failure error paths. Fake SMTP
/// sender (live Gmail gated on Julian's App Password).</summary>
public class SendReplyServiceTests
{
    private sealed class CapturingSender : IGmailMailSender
    {
        public OutgoingMail? Last;
        public bool ThrowAuth;
        public Task SendAsync(MailConnection c, OutgoingMail m, CancellationToken ct = default)
        {
            if (ThrowAuth) throw new AuthenticationException("bad app password");
            Last = m;
            return Task.CompletedTask;
        }
    }

    private static ApplicationContext NewDb()
    {
        var opts = new DbContextOptionsBuilder<ApplicationContext>()
            .UseInMemoryDatabase("send-" + Guid.NewGuid().ToString("N")).Options;
        var db = new ApplicationContext(opts);
        db.Database.EnsureCreated();
        return db;
    }

    private static async Task<(ApplicationContext db, SendReplyService svc, CapturingSender sender, Request req)>
        SetupAsync(bool configured = true, string subject = "Anfrage Rennmäuse", string? refs = "<a@x> <b@y>")
    {
        var db = NewDb();
        var settings = new MailSettingsService(db, new EphemeralDataProtectionProvider());
        var s = await settings.GetAsync();
        if (configured) { s.GmailAddress = "zucht@gmail.com"; settings.SetPassword(s, "app-pw"); }
        var req = new Request
        {
            Id = Guid.NewGuid(),
            GmailMessageId = "<orig@gmail>",
            ReferencesHeader = refs,
            FromAddress = "kunde@web.de",
            Subject = subject,
            Status = RequestStatus.New,
            ReceivedAt = DateTimeOffset.UtcNow,
        };
        db.Requests.Add(req);
        await db.SaveChangesAsync();
        var sender = new CapturingSender();
        return (db, new SendReplyService(db, sender, settings), sender, req);
    }

    [Fact]
    public async Task Send_threads_and_marks_answered()
    {
        var (db, svc, sender, req) = await SetupAsync();

        var outcome = await svc.SendAsync(req, "Hallo, sehr gerne!");

        Assert.True(outcome.Sent);
        Assert.Null(outcome.Error);
        Assert.NotNull(sender.Last);
        Assert.Equal("kunde@web.de", sender.Last!.To);
        Assert.Equal("Re: Anfrage Rennmäuse", sender.Last.Subject);
        Assert.Equal("<orig@gmail>", sender.Last.InReplyTo);
        Assert.Equal("<a@x> <b@y> <orig@gmail>", sender.Last.References);   // chain + original

        var saved = await db.Requests.SingleAsync();
        Assert.Equal(RequestStatus.Answered, saved.Status);
        Assert.NotNull(saved.AnsweredAt);
        Assert.Equal("Hallo, sehr gerne!", saved.DraftReply);
    }

    [Fact]
    public async Task Send_does_not_double_prefix_re_subject()
    {
        var (_, svc, sender, req) = await SetupAsync(subject: "Re: läuft schon", refs: null);
        await svc.SendAsync(req, "ok");
        Assert.Equal("Re: läuft schon", sender.Last!.Subject);
        Assert.Equal("<orig@gmail>", sender.Last.References);   // no prior chain -> just the original
    }

    [Fact]
    public async Task Send_without_config_fails_and_keeps_status()
    {
        var (db, svc, sender, req) = await SetupAsync(configured: false);
        var outcome = await svc.SendAsync(req, "ok");
        Assert.Equal("MailNotConfigured", outcome.Error);
        Assert.Null(sender.Last);
        Assert.Equal(RequestStatus.New, (await db.Requests.SingleAsync()).Status);
    }

    [Fact]
    public async Task Send_auth_failure_does_not_mark_answered()
    {
        var (db, svc, sender, req) = await SetupAsync();
        sender.ThrowAuth = true;
        var outcome = await svc.SendAsync(req, "ok");
        Assert.Equal("MailAuthFailed", outcome.Error);
        Assert.Equal(RequestStatus.New, (await db.Requests.SingleAsync()).Status);
    }

    [Fact]
    public void BuildMimeMessage_sets_threading_headers()
    {
        var msg = GmailMailSender.BuildMimeMessage("zucht@gmail.com",
            new OutgoingMail("kunde@web.de", "Re: Test", "Hallo", "<orig@gmail>", "<a@x> <orig@gmail>"));

        Assert.Equal("Re: Test", msg.Subject);
        Assert.Contains("kunde@web.de", msg.To.ToString());
        Assert.Contains("orig@gmail", msg.InReplyTo);
        Assert.Contains("orig@gmail", string.Join(' ', msg.References));
        Assert.Contains("a@x", string.Join(' ', msg.References));
    }
}
