using System.Net;
using System.Net.Http.Json;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis;
using VigilCare.ClinicalContracts.Sync;

[Collection("Integration")]
public class ClinicalSyncBatchTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private readonly HttpClient _client;
    private Guid _gatewayId;
    private Guid _siteId;
    private Guid _encounterId;

    public ClinicalSyncBatchTests(ApiFixture fixture)
    {
        _fixture = fixture;
        _client = fixture.CreateClient();
    }

    public async Task InitializeAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await DbResetHelper.ResetAsync(db);
        await GatewayRegistrySeeder.SeedAsync(db);
        await DataSeeder.SeedAsync(db, scope.ServiceProvider.GetRequiredService<IConnectionMultiplexer>());
        _gatewayId = GatewayRegistrySeeder.DemoGatewayId;
        _siteId = GatewayRegistrySeeder.DemoSiteId;
        _encounterId = await db.Encounters.Where(e => e.Status == EncounterStatus.Active)
            .Select(e => e.Id).FirstAsync();
    }

    public Task DisposeAsync() => Task.CompletedTask;

    private ClinicalSyncBatchRequest BuildBatch(int obsCount = 1)
    {
        var batchRef = Guid.NewGuid();
        var observations = Enumerable.Range(0, obsCount).Select(i => new SyncedObservation(
            Guid.NewGuid(), $"sync-key-{batchRef}-{i}", _encounterId,
            "HEART_RATE", 80m + i, "bpm", "DEVICE", DateTimeOffset.UtcNow.AddMinutes(-i)
        )).ToList();

        return new ClinicalSyncBatchRequest(
            batchRef, _gatewayId, _siteId, DateTimeOffset.UtcNow,
            observations, [], [], []);
    }

    [Fact]
    public async Task UploadBatch_ReturnsReceived()
    {
        _client.WithGatewayApiKey(_gatewayId);
        var resp = await _client.PostAsJsonAsync("/api/v1/sync/batches", BuildBatch());
        resp.StatusCode.Should().Be(HttpStatusCode.Created);

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        (await db.OutboxEvents.CountAsync(e => e.Topic == "clinical.sync.batch_received"))
            .Should().BeGreaterThan(0);
    }

    [Fact]
    public async Task DuplicateBatchReference_ReturnsExisting()
    {
        _client.WithGatewayApiKey(_gatewayId);
        var batch = BuildBatch();
        var first = await _client.PostAsJsonAsync("/api/v1/sync/batches", batch);
        var firstBody = await first.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>();

        var second = await _client.PostAsJsonAsync("/api/v1/sync/batches", batch);
        var secondBody = await second.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>();

        secondBody!.Data!.BatchId.Should().Be(firstBody!.Data!.BatchId);
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        (await db.ClinicalSyncBatches.CountAsync(b => b.BatchReference == batch.BatchReference))
            .Should().Be(1);
    }

    [Fact]
    public async Task Processor_AppliesObservations()
    {
        _client.WithGatewayApiKey(_gatewayId);
        var upload = await _client.PostAsJsonAsync("/api/v1/sync/batches", BuildBatch(3));
        var batchId = (await upload.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>())!.Data!.BatchId;

        using var scope = _fixture.Services.CreateScope();
        var processor = scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>();
        await processor.ProcessBatchAsync(batchId, CancellationToken.None);

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        (await db.Observations.CountAsync()).Should().BeGreaterThanOrEqualTo(3);
        (await db.OutboxEvents.CountAsync(e => e.Topic == "observation.recorded")).Should().BeGreaterThanOrEqualTo(3);
    }

    [Fact]
    public async Task Processor_ConflictOnAckBeforeAlert()
    {
        var batchRef = Guid.NewGuid();
        var clientAlertId = Guid.NewGuid();
        var batch = new ClinicalSyncBatchRequest(
            batchRef, _gatewayId, _siteId, DateTimeOffset.UtcNow,
            [],
            [],
            [new SyncedAlertAcknowledgment(Guid.NewGuid(), clientAlertId, "RN-Smith",
                DateTimeOffset.UtcNow, null)],
            []);

        _client.WithGatewayApiKey(_gatewayId);
        var upload = await _client.PostAsJsonAsync("/api/v1/sync/batches", batch);
        var batchId = (await upload.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>())!.Data!.BatchId;

        using var scope = _fixture.Services.CreateScope();
        await scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>()
            .ProcessBatchAsync(batchId, CancellationToken.None);

        var status = await _client.GetAsync($"/api/v1/sync/batches/{batchId}");
        var body = await status.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchStatusResponse>>();
        body!.Data!.Status.Should().Be("CONFLICT");
        body.Data.Conflicts.Should().ContainSingle(c => c.ConflictReason == "ALERT_NOT_YET_SYNCED");
    }

    [Fact]
    public async Task Processor_UpdatesGatewayLastSyncAt()
    {
        _client.WithGatewayApiKey(_gatewayId);
        var upload = await _client.PostAsJsonAsync("/api/v1/sync/batches", BuildBatch());
        var batchId = (await upload.Content.ReadFromJsonAsync<ApiResponse<ClinicalBatchUploadResponse>>())!.Data!.BatchId;

        using var scope = _fixture.Services.CreateScope();
        await scope.ServiceProvider.GetRequiredService<ClinicalSyncBatchProcessor>()
            .ProcessBatchAsync(batchId, CancellationToken.None);

        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var gw = await db.WardGateways.FindAsync(_gatewayId);
        gw!.LastSyncAt.Should().NotBeNull();
        gw.ReportedBufferDepth.Should().Be(0);
    }
}