using System.Net;
using System.Text;
using JobTrackerApi.Data;
using JobTrackerApi.Models;
using JobTrackerApi.Services;
using JobTrackerApi.Services.EmailProviders;
using JobTrackerApi.Tests.TestSupport;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
using Moq;
using Xunit;

namespace JobTrackerApi.Tests;

public sealed class EmailProviderDeliveryTests
{
    [Fact]
    public async Task Authorization_urls_request_explicit_send_consent()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var protection = new EphemeralDataProtectionProvider();
        var clients = new ClientFactory(new Handler(_ => throw new InvalidOperationException("No HTTP expected.")));

        var gmail = new GmailOAuthService(Config(), db, protection, clients, new MemoryCache(new MemoryCacheOptions()));
        var graph = new MicrosoftGraphOAuthService(Config(), db, protection, clients, new MemoryCache(new MemoryCacheOptions()));

        Assert.Contains(GmailOAuthService.SendScope, Uri.UnescapeDataString(gmail.BuildAuthorizationUrl("user-1", "https://app.test/gmail")));
        Assert.Contains(MicrosoftGraphOAuthService.SendScope, Uri.UnescapeDataString(graph.BuildAuthorizationUrl("user-1", "https://app.test/graph")));
    }

    [Fact]
    public async Task Gmail_send_uses_send_scope_plain_text_and_thread_without_exposing_provider_errors()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var protection = new EphemeralDataProtectionProvider();
        var protector = protection.CreateProtector("gmail-oauth-tokens-v1");
        db.GmailConnections.Add(new GmailConnection
        {
            OwnerUserId = "user-1",
            GmailAddress = "owner@gmail.test",
            EncryptedAccessToken = protector.Protect("access-token"),
            EncryptedRefreshToken = protector.Protect("refresh-token"),
            AccessTokenExpiresAt = DateTimeOffset.UtcNow.AddHours(1),
            Scope = $"https://www.googleapis.com/auth/gmail.readonly {GmailOAuthService.SendScope}",
        });
        await db.SaveChangesAsync();

        string? captured = null;
        var service = new GmailOAuthService(Config(), db, protection, new ClientFactory(new Handler(async request =>
        {
            Assert.Equal("https://gmail.googleapis.com/gmail/v1/users/me/messages/send", request.RequestUri!.ToString());
            Assert.Equal("access-token", request.Headers.Authorization?.Parameter);
            captured = await request.Content!.ReadAsStringAsync();
            return Json(HttpStatusCode.OK, "{\"id\":\"gmail-message-1\",\"threadId\":\"thread-1\"}");
        })), new MemoryCache(new MemoryCacheOptions()));

        var result = await service.SendAsync("user-1", new GmailSendRequest("recruiter@example.test", "Hei – intervju", "Plain body", "thread-1"), default);

        Assert.Equal("gmail-message-1", result.MessageId);
        Assert.Contains("thread-1", captured);
        Assert.DoesNotContain("Plain body", captured);
    }

    [Fact]
    public async Task Graph_send_uses_mail_send_scope_and_classifies_rejection_as_known()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var protection = new EphemeralDataProtectionProvider();
        var protector = protection.CreateProtector("microsoft-graph-oauth-tokens-v1");
        db.MicrosoftGraphConnections.Add(new MicrosoftGraphConnection
        {
            OwnerUserId = "user-1",
            MailAddress = "owner@outlook.test",
            EncryptedAccessToken = protector.Protect("access-token"),
            EncryptedRefreshToken = protector.Protect("refresh-token"),
            AccessTokenExpiresAt = DateTimeOffset.UtcNow.AddHours(1),
            Scope = "Mail.Read Mail.Send",
        });
        await db.SaveChangesAsync();

        var service = new MicrosoftGraphOAuthService(Config(), db, protection, new ClientFactory(new Handler(request =>
        {
            Assert.Equal("https://graph.microsoft.com/v1.0/me/sendMail", request.RequestUri!.ToString());
            return Task.FromResult(Json(HttpStatusCode.BadRequest, "{\"error\":{\"message\":\"private provider detail\"}}"));
        })), new MemoryCache(new MemoryCacheOptions()));

        var error = await Assert.ThrowsAsync<EmailProviderDeliveryException>(() =>
            service.SendAsync("user-1", new MicrosoftGraphSendRequest("recruiter@example.test", "Subject", "Body"), default));

        Assert.False(error.Uncertain);
        Assert.Equal("provider_rejected", error.Category);
        Assert.DoesNotContain("private provider detail", error.Message);
    }

    [Fact]
    public async Task Transport_interruption_is_uncertain_and_missing_scope_requires_reauthorization()
    {
        await using var db = TestHostFactory.CreateInMemoryDb();
        var protection = new EphemeralDataProtectionProvider();
        var protector = protection.CreateProtector("gmail-oauth-tokens-v1");
        db.GmailConnections.Add(new GmailConnection
        {
            OwnerUserId = "user-1",
            GmailAddress = "owner@gmail.test",
            EncryptedAccessToken = protector.Protect("access-token"),
            EncryptedRefreshToken = protector.Protect("refresh-token"),
            AccessTokenExpiresAt = DateTimeOffset.UtcNow.AddHours(1),
            Scope = GmailOAuthService.SendScope,
        });
        await db.SaveChangesAsync();

        var interrupted = new GmailOAuthService(Config(), db, protection, new ClientFactory(new Handler(_ => throw new HttpRequestException("network detail"))), new MemoryCache(new MemoryCacheOptions()));
        var uncertain = await Assert.ThrowsAsync<EmailProviderDeliveryException>(() =>
            interrupted.SendAsync("user-1", new GmailSendRequest("recruiter@example.test", "Subject", "Body", null), default));
        Assert.True(uncertain.Uncertain);
        Assert.Equal("transport_interrupted", uncertain.Category);
        Assert.DoesNotContain("network detail", uncertain.Message);

        var connection = await db.GmailConnections.SingleAsync();
        connection.Scope = "https://www.googleapis.com/auth/gmail.readonly";
        await db.SaveChangesAsync();
        var reauthorization = await Assert.ThrowsAsync<EmailProviderDeliveryException>(() =>
            interrupted.SendAsync("user-1", new GmailSendRequest("recruiter@example.test", "Subject", "Body", null), default));
        Assert.False(reauthorization.Uncertain);
        Assert.Equal("reauthorization_required", reauthorization.Category);
    }

    private static IConfiguration Config() => new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
    {
        ["Google:ClientId"] = "google-client-test",
        ["Microsoft:ClientId"] = "microsoft-client-test",
    }).Build();
    private static HttpResponseMessage Json(HttpStatusCode status, string body) => new(status) { Content = new StringContent(body, Encoding.UTF8, "application/json") };

    private sealed class ClientFactory(HttpMessageHandler handler) : IHttpClientFactory
    {
        public HttpClient CreateClient(string name) => new(handler, disposeHandler: false);
    }

    private sealed class Handler(Func<HttpRequestMessage, Task<HttpResponseMessage>> handler) : HttpMessageHandler
    {
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) => handler(request);
    }
}
