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

[Collection("Database")]
public class DraftEntryTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private HttpClient _client = null!;
    private Guid _batchId;

    public DraftEntryTests(ApiFixture fixture)
    {
        _fixture = fixture;
    }

    public async Task InitializeAsync()
    {
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        await DbResetHelper.ResetAsync(db);

        // Re-seed users — truncation removed them
        await DataSeeder.SeedAsync(db);

        // Create a batch directly in the database in UPLOADED status.
        // We skip the MinIO upload path because integration tests focus on
        // draft entry logic, not document storage (covered in Phase 1 tests).
        var batch = new DigitizationBatch
        {
            Id = Guid.NewGuid(),
            Status = BatchStatus.Uploaded,
            BatchType = BatchType.VitalsSheet,
            Track = BatchTrack.Backfill,
            DocumentRef = "test/scan.pdf",
            DocumentSha256 = "abc123def456",
            CreatedAt = DateTimeOffset.UtcNow,
            UpdatedAt = DateTimeOffset.UtcNow
        };
        db.DigitizationBatches.Add(batch);

        // Seed a ScannedDocument to satisfy the foreign key
        db.ScannedDocuments.Add(new ScannedDocument
        {
            Id = Guid.NewGuid(),
            BatchId = batch.Id,
            ObjectKey = "test/scan.pdf",
            Sha256 = "abc123def456",
            ContentType = "application/pdf",
            FileSizeBytes = 1024,
            UploadedAt = DateTimeOffset.UtcNow
        });

        await db.SaveChangesAsync();
        _batchId = batch.Id;

        // Authenticate as entry clerk
        _client = await AuthHelper.LoginAsync(_fixture, "entry1");
    }

    public Task DisposeAsync() => Task.CompletedTask;

    // ─── Test 1: Incomplete vitals batch cannot submit ────────────

    [Fact]
    public async Task IncompleteVitalsBatch_CannotSubmit_Returns422()
    {
        // Attempt to submit an empty batch — no patient, no encounter, no observations
        // First, we need to get it into IN_ENTRY status by saving something
        await _client.PutAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/patient",
            new { fullName = "Test Patient" });

        // Now attempt to submit — should fail because:
        // - Patient missing DOB and sex (but we only need patient for vitals)
        // - No encounter context
        // - No observations
        var submitResp = await _client.PostAsync(
            $"/api/v1/digitization-batches/{_batchId}/submit-for-verification", null);

        submitResp.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);

        var body = await submitResp.Content.ReadFromJsonAsync<JsonDocument>();
        var errorCode = body!.RootElement
            .GetProperty("error").GetProperty("code").GetString();
        errorCode.Should().Be("BATCH_INCOMPLETE");

        var errorMsg = body.RootElement
            .GetProperty("error").GetProperty("message").GetString();
        errorMsg.Should().Contain("Encounter context is required");
        errorMsg.Should().Contain("At least one observation");

        // Verify batch is still IN_ENTRY, not PendingVerification
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var batch = await db.DigitizationBatches.FindAsync(_batchId);
        batch!.Status.Should().Be(BatchStatus.InEntry);
    }

    // ─── Test 2: Plausible observations save successfully ────────

    [Fact]
    public async Task PlausibleObservation_SavesSuccessfully_Returns201()
    {
        // Heart rate 78 bpm is well within [1-300] plausible range
        var resp = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations",
            new
            {
                observationCode = "HEART_RATE",
                value = 78,
                unit = "bpm",
                recordedAt = DateTimeOffset.UtcNow,
                note = "Resting heart rate from chart"
            });

        resp.StatusCode.Should().Be(HttpStatusCode.Created);

        var body = await resp.Content.ReadFromJsonAsync<JsonDocument>();
        body!.RootElement.GetProperty("data").GetProperty("observationCode").GetString()
            .Should().Be("HEART_RATE");
        body.RootElement.GetProperty("data").GetProperty("value").GetDecimal()
            .Should().Be(78);

        // Verify the observation was persisted
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var obsCount = await db.DraftObservations.CountAsync(o => o.BatchId == _batchId);
        obsCount.Should().Be(1);

        // Verify batch transitioned to IN_ENTRY
        var batch = await db.DigitizationBatches.FindAsync(_batchId);
        batch!.Status.Should().Be(BatchStatus.InEntry);

        // Verify entry_started event was logged
        var events = await db.DigitizationEvents
            .Where(e => e.BatchId == _batchId && e.EventType == DigitizationEventType.EntryStarted)
            .ToListAsync();
        events.Should().HaveCount(1);
    }

    // ─── Test 3: Implausible observations are rejected ───────────

    [Fact]
    public async Task ImplausibleObservation_Returns422_NoRowWritten()
    {
        // Heart rate 350 bpm is above the plausibility ceiling of 300
        var resp = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations",
            new
            {
                observationCode = "HEART_RATE",
                value = 350,
                unit = "bpm",
                recordedAt = DateTimeOffset.UtcNow
            });

        resp.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity);

        var body = await resp.Content.ReadFromJsonAsync<JsonDocument>();
        body!.RootElement.GetProperty("error").GetProperty("code").GetString()
            .Should().Be("OBSERVATION_OUT_OF_PLAUSIBLE_RANGE");

        // Verify no observation was persisted
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var obsCount = await db.DraftObservations.CountAsync(o => o.BatchId == _batchId);
        obsCount.Should().Be(0, "implausible observations must not be persisted");
    }

    // ─── Test 4: Full draft lifecycle — entry through submit ─────

    [Fact]
    public async Task FullDraftLifecycle_EntryThroughSubmit()
    {
        // 1. Upsert patient
        var patientResp = await _client.PutAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/patient",
            new
            {
                fullName = "Chen Wei-Lin",
                dateOfBirth = "1985-03-15",
                sex = "M",
                bloodType = "O+",
                noKnownAllergies = true
            });
        patientResp.StatusCode.Should().Be(HttpStatusCode.OK);

        // 2. Upsert encounter
        var encounterResp = await _client.PutAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/encounter",
            new
            {
                admissionDate = DateTimeOffset.UtcNow.AddDays(-2),
                department = "ICU",
                roomBed = "ICU-3B",
                admissionReason = "Chest pain"
            });
        encounterResp.StatusCode.Should().Be(HttpStatusCode.OK);

        // 3. Add plausible observations
        var obs1Resp = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations",
            new
            {
                observationCode = "HEART_RATE",
                value = 92,
                unit = "bpm",
                recordedAt = DateTimeOffset.UtcNow.AddHours(-1)
            });
        obs1Resp.StatusCode.Should().Be(HttpStatusCode.Created);

        var obs2Resp = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations",
            new
            {
                observationCode = "BP_SYSTOLIC",
                value = 138,
                unit = "mmHg",
                recordedAt = DateTimeOffset.UtcNow.AddHours(-1)
            });
        obs2Resp.StatusCode.Should().Be(HttpStatusCode.Created);

        // 4. Verify GET /draft returns all data
        var draftResp = await _client.GetAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft");
        draftResp.StatusCode.Should().Be(HttpStatusCode.OK);

        var draftBody = await draftResp.Content.ReadFromJsonAsync<JsonDocument>();
        draftBody!.RootElement.GetProperty("data").GetProperty("patient")
            .GetProperty("fullName").GetString().Should().Be("Chen Wei-Lin");
        draftBody.RootElement.GetProperty("data").GetProperty("encounter")
            .GetProperty("department").GetString().Should().Be("ICU");
        draftBody.RootElement.GetProperty("data").GetProperty("observations")
            .GetArrayLength().Should().Be(2);

        // 5. Submit for verification — should succeed
        var submitResp = await _client.PostAsync(
            $"/api/v1/digitization-batches/{_batchId}/submit-for-verification", null);
        submitResp.StatusCode.Should().Be(HttpStatusCode.OK);

        // 6. Verify batch is now PENDING_VERIFICATION
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var batch = await db.DigitizationBatches.FindAsync(_batchId);
        batch!.Status.Should().Be(BatchStatus.PendingVerification);

        // 7. Verify submitted_for_verification event was logged
        var submitEvent = await db.DigitizationEvents
            .FirstOrDefaultAsync(e =>
                e.BatchId == _batchId &&
                e.EventType == DigitizationEventType.SubmittedForVerification);
        submitEvent.Should().NotBeNull();
    }

    // ─── Test 5: Observation CRUD — add, update, delete ──────────

    [Fact]
    public async Task ObservationCrud_AddUpdateDelete()
    {
        // Add
        var addResp = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations",
            new
            {
                observationCode = "TEMP_C",
                value = 37.2,
                unit = "C",
                recordedAt = DateTimeOffset.UtcNow,
                note = "Oral temperature"
            });
        addResp.StatusCode.Should().Be(HttpStatusCode.Created);

        var addBody = await addResp.Content.ReadFromJsonAsync<JsonDocument>();
        var obsId = addBody!.RootElement.GetProperty("data").GetProperty("id").GetGuid();

        // Update — correct the value (clerk misread the chart)
        var updateResp = await _client.PutAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations/{obsId}",
            new
            {
                observationCode = "TEMP_C",
                value = 38.1,
                unit = "C",
                recordedAt = DateTimeOffset.UtcNow,
                note = "Corrected — misread decimal"
            });
        updateResp.StatusCode.Should().Be(HttpStatusCode.OK);

        var updateBody = await updateResp.Content.ReadFromJsonAsync<JsonDocument>();
        updateBody!.RootElement.GetProperty("data").GetProperty("value").GetDecimal()
            .Should().Be(38.1m);
        updateBody.RootElement.GetProperty("data").GetProperty("note").GetString()
            .Should().Contain("Corrected");

        // Delete
        var deleteResp = await _client.DeleteAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations/{obsId}");
        deleteResp.StatusCode.Should().Be(HttpStatusCode.NoContent);

        // Verify observation is gone
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var obsCount = await db.DraftObservations.CountAsync(o => o.BatchId == _batchId);
        obsCount.Should().Be(0);
    }

    // ─── Test 6: Rejected batch re-entry ─────────────────────────

    [Fact]
    public async Task RejectedBatch_CanReEnter_TransitionsToInEntry()
    {
        // Manually set batch to REJECTED status (simulating a verifier rejection)
        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var batch = await db.DigitizationBatches.FindAsync(_batchId);
            batch!.Status = BatchStatus.Rejected;
            batch.RejectionReason = "Missing encounter details";
            await db.SaveChangesAsync();
        }

        // Data entry clerk corrects the entry — should transition from REJECTED to IN_ENTRY
        var resp = await _client.PutAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/encounter",
            new
            {
                admissionDate = DateTimeOffset.UtcNow.AddDays(-1),
                department = "Emergency Department",
                roomBed = "ER-7"
            });

        resp.StatusCode.Should().Be(HttpStatusCode.OK);

        // Verify batch is now IN_ENTRY (transitioned from REJECTED)
        using var verifyScope = _fixture.Services.CreateScope();
        var verifyDb = verifyScope.ServiceProvider.GetRequiredService<AppDbContext>();
        var verifyBatch = await verifyDb.DigitizationBatches.FindAsync(_batchId);
        verifyBatch!.Status.Should().Be(BatchStatus.InEntry);

        // Verify the entry_started event recorded the previous status
        var evt = await verifyDb.DigitizationEvents
            .Where(e => e.BatchId == _batchId && e.EventType == DigitizationEventType.EntryStarted)
            .OrderByDescending(e => e.OccurredAt)
            .FirstOrDefaultAsync();
        evt.Should().NotBeNull();
        evt!.MetadataJson.Should().Contain("REJECTED");
    }

    // ─── Test 7: Data entry blocked on verified batch ────────────

    [Fact]
    public async Task VerifiedBatch_DataEntryBlocked_Returns409()
    {
        // Manually set batch to VERIFIED status
        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var batch = await db.DigitizationBatches.FindAsync(_batchId);
            batch!.Status = BatchStatus.Verified;
            await db.SaveChangesAsync();
        }

        // Attempt to add an observation — should be blocked
        var resp = await _client.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{_batchId}/draft/observations",
            new
            {
                observationCode = "HEART_RATE",
                value = 80,
                unit = "bpm",
                recordedAt = DateTimeOffset.UtcNow
            });

        resp.StatusCode.Should().Be(HttpStatusCode.Conflict);

        var body = await resp.Content.ReadFromJsonAsync<JsonDocument>();
        body!.RootElement.GetProperty("error").GetProperty("code").GetString()
            .Should().Be("ENTRY_NOT_ALLOWED");
    }
}