using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

/// <summary>
/// Integration tests for deferred promotion and retry paths.
/// Validates that the PromoteAsync retry path creates complete clinical entities
/// (Patient, Encounter, Observation, OutboxEvent) — the P0 fix.
/// </summary>
[Collection("Database")]
public class PromotionRetryTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private HttpClient _client = null!;

    public PromotionRetryTests(ApiFixture fixture) => _fixture = fixture;

    public async Task InitializeAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await DbResetHelper.ResetAsync(db);
        await DataSeeder.SeedAsync(db);
        _client = await AuthHelper.LoginAsync(_fixture, "approver1");
    }

    public Task DisposeAsync() => Task.CompletedTask;

    /// <summary>
    /// Simulates a deferred promotion by manually setting a batch to APPROVED status
    /// (as the controller would after an infrastructure failure), then calls PromoteAsync
    /// via the POST .../promote endpoint and verifies all clinical entities are created.
    /// </summary>
    [Fact]
    public async Task PromoteAsync_AfterDeferral_CreatesAllClinicalEntities()
    {
        // Arrange: drive batch through to verified
        var batchId = await BatchPipelineHelper.CreateAndVerifyBatchAsync(_fixture);

        // Simulate what DeferPromotionAsync does: set batch to APPROVED with retry metadata
        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var batch = await db.DigitizationBatches.FindAsync(batchId);
            batch!.Status = BatchStatus.Approved;
            batch.ApprovedByUserId = (await db.Users.FirstAsync(u => u.Username == "approver1")).Id;
            batch.EnableRetroactiveAlerts = true;
            batch.UpdatedAt = DateTimeOffset.UtcNow;

            db.PromotionAttempts.Add(new PromotionAttempt
            {
                Id = Guid.NewGuid(),
                BatchId = batchId,
                AttemptNumber = 1,
                Succeeded = false,
                ErrorMessage = "Simulated infrastructure failure",
                AttemptedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
                NextRetryAt = DateTimeOffset.UtcNow.AddMinutes(-1)
            });

            await db.SaveChangesAsync();
        }

        // Act: call the promote endpoint (same path PromotionRetryService uses)
        var response = await _client.PostAsync(
            $"/api/v1/digitization-batches/{batchId}/promote", null);

        // Assert: HTTP 200
        response.StatusCode.Should().Be(HttpStatusCode.OK);

        var body = await response.Content.ReadFromJsonAsync<JsonElement>();
        var data = body.GetProperty("data");
        data.GetProperty("batchId").GetGuid().Should().Be(batchId);
        data.GetProperty("patientId").GetGuid().Should().NotBeEmpty();
        data.GetProperty("mrn").GetString().Should().StartWith("VCR-");
        data.GetProperty("encounterId").GetGuid().Should().NotBeEmpty();

        // Verify clinical entities in database
        using var verifyScope = _fixture.Services.CreateScope();
        var verifyDb = verifyScope.ServiceProvider.GetRequiredService<AppDbContext>();

        // Batch is PROMOTED
        var promotedBatch = await verifyDb.DigitizationBatches.FindAsync(batchId);
        promotedBatch!.Status.Should().Be(BatchStatus.Promoted);
        promotedBatch.PatientId.Should().NotBeNull();
        promotedBatch.PromotionEncounterId.Should().NotBeNull();

        // Patient exists
        var patient = await verifyDb.Patients.FindAsync(promotedBatch.PatientId!.Value);
        patient.Should().NotBeNull();
        patient!.Mrn.Should().StartWith("VCR-");

        // Clinical Encounter exists
        var encounter = await verifyDb.Encounters.FindAsync(promotedBatch.PromotionEncounterId!.Value);
        encounter.Should().NotBeNull();
        encounter!.PatientId.Should().Be(patient.Id);

        // Clinical Observations exist
        var observations = await verifyDb.Observations
            .Where(o => o.SourceBatchId == batchId)
            .ToListAsync();
        observations.Should().HaveCount(3);
        observations.Should().AllSatisfy(o =>
        {
            o.PatientId.Should().Be(patient.Id);
            o.EncounterId.Should().Be(encounter.Id);
        });

        // OutboxEvents exist (enableRetroactiveAlerts was true)
        var outboxEvents = await verifyDb.OutboxEvents
            .Where(e => observations.Select(o => o.Id).Contains(e.AggregateId))
            .ToListAsync();
        outboxEvents.Should().HaveCount(3,
            "enableRetroactiveAlerts=true should create outbox events on retry path");

        // LiveObservations exist
        var liveObs = await verifyDb.LiveObservations
            .Where(o => o.SourceBatchId == batchId)
            .ToListAsync();
        liveObs.Should().HaveCount(3);

        // LiveEncounter exists
        var liveEnc = await verifyDb.LiveEncounters.FindAsync(encounter.Id);
        liveEnc.Should().NotBeNull();
    }

    /// <summary>
    /// Verifies that PromoteAsync without retroactive alerts creates no outbox events.
    /// </summary>
    [Fact]
    public async Task PromoteAsync_NoRetroactiveAlerts_CreatesZeroOutboxEvents()
    {
        var batchId = await BatchPipelineHelper.CreateAndVerifyBatchAsync(
            _fixture, patientFullName: "No Alerts Patient", patientDateOfBirth: "1985-03-20");

        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var batch = await db.DigitizationBatches.FindAsync(batchId);
            batch!.Status = BatchStatus.Approved;
            batch.ApprovedByUserId = (await db.Users.FirstAsync(u => u.Username == "approver1")).Id;
            batch.EnableRetroactiveAlerts = false;
            batch.UpdatedAt = DateTimeOffset.UtcNow;
            await db.SaveChangesAsync();
        }

        var response = await _client.PostAsync(
            $"/api/v1/digitization-batches/{batchId}/promote", null);
        response.StatusCode.Should().Be(HttpStatusCode.OK);

        using var verifyScope = _fixture.Services.CreateScope();
        var verifyDb = verifyScope.ServiceProvider.GetRequiredService<AppDbContext>();

        var observations = await verifyDb.Observations
            .Where(o => o.SourceBatchId == batchId)
            .ToListAsync();
        observations.Should().HaveCount(3);

        var outboxEvents = await verifyDb.OutboxEvents
            .Where(e => observations.Select(o => o.Id).Contains(e.AggregateId))
            .ToListAsync();
        outboxEvents.Should().BeEmpty(
            "enableRetroactiveAlerts=false should write zero outbox events on retry");
    }

    /// <summary>
    /// Verifies that PromoteAsync on a non-APPROVED batch returns 409.
    /// </summary>
    [Fact]
    public async Task PromoteAsync_WrongStatus_Returns409()
    {
        var batchId = await BatchPipelineHelper.CreateAndVerifyBatchAsync(
            _fixture, patientFullName: "Wrong Status Patient", patientDateOfBirth: "1992-07-10");

        // Batch is in VERIFIED status (not APPROVED)
        var response = await _client.PostAsync(
            $"/api/v1/digitization-batches/{batchId}/promote", null);

        response.StatusCode.Should().Be(HttpStatusCode.Conflict);
    }

    /// <summary>
    /// Verifies that patient deduplication works on the retry path —
    /// promoting two batches for the same patient (same name + DOB) should
    /// reuse the same Patient/MRN rather than creating a duplicate.
    /// </summary>
    [Fact]
    public async Task PromoteAsync_SamePatient_DeduplicatesOnRetry()
    {
        // Create and promote batch 1 via normal approve path
        var batchId1 = await BatchPipelineHelper.CreateAndVerifyBatchAsync(
            _fixture, patientFullName: "Dedup Patient", patientDateOfBirth: "1988-11-01");

        _client.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
        var approve1 = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId1}/approve",
            new ApproveRequest());
        approve1.EnsureSuccessStatusCode();
        _client.DefaultRequestHeaders.Remove("Idempotency-Key");

        var result1 = await approve1.Content.ReadFromJsonAsync<JsonElement>();
        var patientId1 = result1.GetProperty("data").GetProperty("patientId").GetGuid();
        var mrn1 = result1.GetProperty("data").GetProperty("mrn").GetString();

        // Create batch 2 for same patient, defer to APPROVED, then promote
        var batchId2 = await BatchPipelineHelper.CreateAndVerifyBatchAsync(
            _fixture, patientFullName: "Dedup Patient", patientDateOfBirth: "1988-11-01");

        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var batch = await db.DigitizationBatches.FindAsync(batchId2);
            batch!.Status = BatchStatus.Approved;
            batch.ApprovedByUserId = (await db.Users.FirstAsync(u => u.Username == "approver1")).Id;
            batch.EnableRetroactiveAlerts = false;
            batch.UpdatedAt = DateTimeOffset.UtcNow;
            await db.SaveChangesAsync();
        }

        var promote2 = await _client.PostAsync(
            $"/api/v1/digitization-batches/{batchId2}/promote", null);
        promote2.EnsureSuccessStatusCode();

        // Verify same patient was reused
        using var verifyScope = _fixture.Services.CreateScope();
        var verifyDb = verifyScope.ServiceProvider.GetRequiredService<AppDbContext>();
        var batch2 = await verifyDb.DigitizationBatches.FindAsync(batchId2);
        batch2!.PatientId.Should().Be(patientId1,
            "retry path should deduplicate and reuse the same patient");

        var patient = await verifyDb.Patients.FindAsync(patientId1);
        patient!.Mrn.Should().Be(mrn1);
    }
}
