using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using NSubstitute.ExceptionExtensions;
using ScadaLink.Commons.Entities.Notifications;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.StoreAndForward;

namespace ScadaLink.NotificationService.Tests;

/// <summary>
/// WP-11/12: Tests for notification delivery — SMTP delivery, error classification, S&amp;F integration.
/// </summary>
public class NotificationDeliveryServiceTests
{
    private readonly INotificationRepository _repository = Substitute.For<INotificationRepository>();
    private readonly ISmtpClientWrapper _smtpClient = Substitute.For<ISmtpClientWrapper>();

    private NotificationDeliveryService CreateService(StoreAndForward.StoreAndForwardService? sf = null)
    {
        return new NotificationDeliveryService(
            _repository,
            () => _smtpClient,
            NullLogger<NotificationDeliveryService>.Instance,
            tokenService: null,
            storeAndForward: sf);
    }

    private void SetupHappyPath()
    {
        var list = new NotificationList("ops-team") { Id = 1 };
        var recipients = new List<NotificationRecipient>
        {
            new("Alice", "alice@example.com") { Id = 1, NotificationListId = 1 },
            new("Bob", "bob@example.com") { Id = 2, NotificationListId = 1 }
        };
        var smtpConfig = new SmtpConfiguration("smtp.example.com", "basic", "noreply@example.com")
        {
            Id = 1, Port = 587, Credentials = "user:pass", TlsMode = "starttls"
        };

        _repository.GetListByNameAsync("ops-team").Returns(list);
        _repository.GetRecipientsByListIdAsync(1).Returns(recipients);
        _repository.GetAllSmtpConfigurationsAsync().Returns(new List<SmtpConfiguration> { smtpConfig });
    }

    [Fact]
    public async Task Send_ListNotFound_ReturnsError()
    {
        _repository.GetListByNameAsync("nonexistent").Returns((NotificationList?)null);
        var service = CreateService();

        var result = await service.SendAsync("nonexistent", "Subject", "Body");

        Assert.False(result.Success);
        Assert.Contains("not found", result.ErrorMessage);
    }

    [Fact]
    public async Task Send_NoRecipients_ReturnsError()
    {
        var list = new NotificationList("empty-list") { Id = 1 };
        _repository.GetListByNameAsync("empty-list").Returns(list);
        _repository.GetRecipientsByListIdAsync(1).Returns(new List<NotificationRecipient>());

        var service = CreateService();
        var result = await service.SendAsync("empty-list", "Subject", "Body");

        Assert.False(result.Success);
        Assert.Contains("no recipients", result.ErrorMessage);
    }

    [Fact]
    public async Task Send_NoSmtpConfig_ReturnsError()
    {
        var list = new NotificationList("test") { Id = 1 };
        var recipients = new List<NotificationRecipient>
        {
            new("Alice", "alice@example.com") { Id = 1, NotificationListId = 1 }
        };
        _repository.GetListByNameAsync("test").Returns(list);
        _repository.GetRecipientsByListIdAsync(1).Returns(recipients);
        _repository.GetAllSmtpConfigurationsAsync().Returns(new List<SmtpConfiguration>());

        var service = CreateService();
        var result = await service.SendAsync("test", "Subject", "Body");

        Assert.False(result.Success);
        Assert.Contains("No SMTP configuration", result.ErrorMessage);
    }

    [Fact]
    public async Task Send_Successful_ReturnsSuccess()
    {
        SetupHappyPath();
        var service = CreateService();

        var result = await service.SendAsync("ops-team", "Alert", "Something happened");

        Assert.True(result.Success);
        Assert.Null(result.ErrorMessage);
        Assert.False(result.WasBuffered);
    }

    [Fact]
    public async Task Send_SmtpConnectsWithCorrectParams()
    {
        SetupHappyPath();
        var service = CreateService();

        await service.SendAsync("ops-team", "Alert", "Body");

        await _smtpClient.Received().ConnectAsync("smtp.example.com", 587, true, Arg.Any<CancellationToken>());
        await _smtpClient.Received().AuthenticateAsync("basic", "user:pass", Arg.Any<CancellationToken>());
        await _smtpClient.Received().SendAsync(
            "noreply@example.com",
            Arg.Is<IEnumerable<string>>(bcc => bcc.Count() == 2),
            "Alert",
            "Body",
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Send_PermanentSmtpError_ReturnsErrorDirectly()
    {
        SetupHappyPath();
        _smtpClient.SendAsync(Arg.Any<string>(), Arg.Any<IEnumerable<string>>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Throws(new SmtpPermanentException("550 Mailbox not found"));

        var service = CreateService();
        var result = await service.SendAsync("ops-team", "Alert", "Body");

        Assert.False(result.Success);
        Assert.Contains("Permanent SMTP error", result.ErrorMessage);
    }

    [Fact]
    public async Task Send_TransientError_NoStoreAndForward_ReturnsError()
    {
        SetupHappyPath();
        _smtpClient.SendAsync(Arg.Any<string>(), Arg.Any<IEnumerable<string>>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Throws(new TimeoutException("Connection timed out"));

        var service = CreateService(sf: null);
        var result = await service.SendAsync("ops-team", "Alert", "Body");

        Assert.False(result.Success);
        Assert.Contains("store-and-forward not available", result.ErrorMessage);
    }

    [Fact]
    public async Task Send_UsesBccDelivery_AllRecipientsInBcc()
    {
        SetupHappyPath();
        IEnumerable<string>? capturedBcc = null;
        _smtpClient.SendAsync(
            Arg.Any<string>(),
            Arg.Do<IEnumerable<string>>(bcc => capturedBcc = bcc),
            Arg.Any<string>(),
            Arg.Any<string>(),
            Arg.Any<CancellationToken>())
            .Returns(Task.CompletedTask);

        var service = CreateService();
        await service.SendAsync("ops-team", "Alert", "Body");

        Assert.NotNull(capturedBcc);
        var bccList = capturedBcc!.ToList();
        Assert.Equal(2, bccList.Count);
        Assert.Contains("alice@example.com", bccList);
        Assert.Contains("bob@example.com", bccList);
    }

    [Fact]
    public async Task Send_TransientError_WithStoreAndForward_BuffersMessage()
    {
        SetupHappyPath();
        _smtpClient.SendAsync(Arg.Any<string>(), Arg.Any<IEnumerable<string>>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Throws(new TimeoutException("Connection timed out"));

        var dbName = $"file:sf_test_{Guid.NewGuid():N}?mode=memory&cache=shared";
        var storage = new StoreAndForward.StoreAndForwardStorage(
            $"Data Source={dbName}", NullLogger<StoreAndForward.StoreAndForwardStorage>.Instance);
        await storage.InitializeAsync();

        var sfOptions = new StoreAndForward.StoreAndForwardOptions();
        var sfService = new StoreAndForward.StoreAndForwardService(
            storage, sfOptions, NullLogger<StoreAndForward.StoreAndForwardService>.Instance);

        var service = CreateService(sf: sfService);
        var result = await service.SendAsync("ops-team", "Alert", "Body");

        Assert.True(result.Success);
        Assert.True(result.WasBuffered);
    }
}
