using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging.Abstractions;
using ScadaLink.Commons.Types.Enums;

namespace ScadaLink.StoreAndForward.Tests;

/// <summary>
/// WP-11: Tests for async replication to standby.
/// </summary>
public class ReplicationServiceTests : IAsyncLifetime, IDisposable
{
    private readonly SqliteConnection _keepAlive;
    private readonly StoreAndForwardStorage _storage;
    private readonly ReplicationService _replicationService;

    public ReplicationServiceTests()
    {
        var dbName = $"RepTests_{Guid.NewGuid():N}";
        var connStr = $"Data Source={dbName};Mode=Memory;Cache=Shared";
        _keepAlive = new SqliteConnection(connStr);
        _keepAlive.Open();

        _storage = new StoreAndForwardStorage(connStr, NullLogger<StoreAndForwardStorage>.Instance);

        var options = new StoreAndForwardOptions { ReplicationEnabled = true };
        _replicationService = new ReplicationService(
            options, NullLogger<ReplicationService>.Instance);
    }

    public async Task InitializeAsync() => await _storage.InitializeAsync();

    public Task DisposeAsync() => Task.CompletedTask;

    public void Dispose() => _keepAlive.Dispose();

    [Fact]
    public void ReplicateEnqueue_NoHandler_DoesNotThrow()
    {
        var msg = CreateMessage("rep1");
        _replicationService.ReplicateEnqueue(msg);
    }

    [Fact]
    public async Task ReplicateEnqueue_WithHandler_ForwardsOperation()
    {
        ReplicationOperation? captured = null;
        _replicationService.SetReplicationHandler(op =>
        {
            captured = op;
            return Task.CompletedTask;
        });

        var msg = CreateMessage("rep2");
        _replicationService.ReplicateEnqueue(msg);

        await Task.Delay(200);

        Assert.NotNull(captured);
        Assert.Equal(ReplicationOperationType.Add, captured!.OperationType);
        Assert.Equal("rep2", captured.MessageId);
    }

    [Fact]
    public async Task ReplicateRemove_WithHandler_ForwardsRemoveOperation()
    {
        ReplicationOperation? captured = null;
        _replicationService.SetReplicationHandler(op =>
        {
            captured = op;
            return Task.CompletedTask;
        });

        _replicationService.ReplicateRemove("rep3");

        await Task.Delay(200);

        Assert.NotNull(captured);
        Assert.Equal(ReplicationOperationType.Remove, captured!.OperationType);
        Assert.Equal("rep3", captured.MessageId);
    }

    [Fact]
    public async Task ReplicatePark_WithHandler_ForwardsParkOperation()
    {
        ReplicationOperation? captured = null;
        _replicationService.SetReplicationHandler(op =>
        {
            captured = op;
            return Task.CompletedTask;
        });

        var msg = CreateMessage("rep4");
        _replicationService.ReplicatePark(msg);

        await Task.Delay(200);

        Assert.NotNull(captured);
        Assert.Equal(ReplicationOperationType.Park, captured!.OperationType);
    }

    [Fact]
    public async Task ApplyReplicatedOperationAsync_Add_EnqueuesMessage()
    {
        var msg = CreateMessage("apply1");
        var operation = new ReplicationOperation(ReplicationOperationType.Add, "apply1", msg);

        await _replicationService.ApplyReplicatedOperationAsync(operation, _storage);

        var retrieved = await _storage.GetMessageByIdAsync("apply1");
        Assert.NotNull(retrieved);
    }

    [Fact]
    public async Task ApplyReplicatedOperationAsync_Remove_DeletesMessage()
    {
        var msg = CreateMessage("apply2");
        await _storage.EnqueueAsync(msg);

        var operation = new ReplicationOperation(ReplicationOperationType.Remove, "apply2", null);
        await _replicationService.ApplyReplicatedOperationAsync(operation, _storage);

        var retrieved = await _storage.GetMessageByIdAsync("apply2");
        Assert.Null(retrieved);
    }

    [Fact]
    public async Task ApplyReplicatedOperationAsync_Park_UpdatesStatus()
    {
        var msg = CreateMessage("apply3");
        await _storage.EnqueueAsync(msg);

        var operation = new ReplicationOperation(ReplicationOperationType.Park, "apply3", msg);
        await _replicationService.ApplyReplicatedOperationAsync(operation, _storage);

        var retrieved = await _storage.GetMessageByIdAsync("apply3");
        Assert.NotNull(retrieved);
        Assert.Equal(StoreAndForwardMessageStatus.Parked, retrieved!.Status);
    }

    [Fact]
    public void ReplicateEnqueue_WhenReplicationDisabled_DoesNothing()
    {
        var options = new StoreAndForwardOptions { ReplicationEnabled = false };
        var service = new ReplicationService(options, NullLogger<ReplicationService>.Instance);

        bool handlerCalled = false;
        service.SetReplicationHandler(_ => { handlerCalled = true; return Task.CompletedTask; });

        service.ReplicateEnqueue(CreateMessage("disabled1"));

        Assert.False(handlerCalled);
    }

    [Fact]
    public async Task ReplicateEnqueue_HandlerThrows_DoesNotPropagateException()
    {
        _replicationService.SetReplicationHandler(_ =>
            throw new InvalidOperationException("standby down"));

        _replicationService.ReplicateEnqueue(CreateMessage("err1"));

        await Task.Delay(200);
        // No exception -- fire-and-forget, best-effort
    }

    private static StoreAndForwardMessage CreateMessage(string id)
    {
        return new StoreAndForwardMessage
        {
            Id = id,
            Category = StoreAndForwardCategory.ExternalSystem,
            Target = "target",
            PayloadJson = "{}",
            RetryCount = 0,
            MaxRetries = 50,
            RetryIntervalMs = 30000,
            CreatedAt = DateTimeOffset.UtcNow,
            Status = StoreAndForwardMessageStatus.Pending
        };
    }
}
