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

/// <summary>
/// Tests for batch cancellation and sort parameters.
/// </summary>
[Collection("Database")]
public class BatchOperationsTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private HttpClient _adminClient = null!;

    public BatchOperationsTests(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);
        _adminClient = await AuthHelper.LoginAsync(_fixture, "admin1");
    }

    public Task DisposeAsync() => Task.CompletedTask;

    // ---------------------------------------------------------------
    // Cancellation tests
    // ---------------------------------------------------------------

    [Fact]
    public async Task Cancel_UploadedBatch_Returns200AndTransitionsToCancelled()
    {
        // Arrange: upload a batch
        var batchId = await UploadBatchAsync();

        // Act
        var response = await _adminClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/cancel",
            new { Reason = "Wrong document scanned" });

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

        var body = await response.Content.ReadFromJsonAsync<JsonElement>();
        body.GetProperty("data").GetProperty("status").GetString()
            .Should().Be("CANCELLED");

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var batch = await db.DigitizationBatches.FindAsync(batchId);
        batch!.Status.Should().Be(BatchStatus.Cancelled);

        var cancelEvent = await db.DigitizationEvents
            .FirstOrDefaultAsync(e => e.BatchId == batchId
                && e.EventType == DigitizationEventType.Cancelled);
        cancelEvent.Should().NotBeNull();
    }

    [Fact]
    public async Task Cancel_InEntryBatch_Returns200()
    {
        // Arrange: upload + assign (transitions to IN_ENTRY)
        var batchId = await UploadBatchAsync();

        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var entryUserId = await BatchSeedHelper.UserIdAsync(db, "entry1");

        await _adminClient.PatchAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/assign",
            new { EntryClerkUserId = entryUserId });

        // Act
        var response = await _adminClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/cancel",
            new { Reason = "Test upload during training" });

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

        // Reload to verify
        db.ChangeTracker.Clear();
        var batch = await db.DigitizationBatches.FindAsync(batchId);
        batch!.Status.Should().Be(BatchStatus.Cancelled);
    }

    [Fact]
    public async Task Cancel_RejectedBatch_Returns200()
    {
        // Arrange: seed a rejected batch
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var entryUserId = await BatchSeedHelper.UserIdAsync(db, "entry1");
        var rejectedBatch = await BatchSeedHelper.SeedBatchInRejectedAsync(db, entryUserId);

        // Act
        var response = await _adminClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{rejectedBatch.Id}/cancel",
            new { Reason = "Patient linked incorrectly" });

        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
    }

    [Fact]
    public async Task Cancel_PromotedBatch_Returns409()
    {
        // Arrange: drive batch to promoted
        var batchId = await BatchPipelineHelper.CreateAndVerifyBatchAsync(_fixture);
        var approverClient = await AuthHelper.LoginAsync(_fixture, "approver1");
        approverClient.DefaultRequestHeaders.Add("Idempotency-Key", Guid.NewGuid().ToString());
        await approverClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/approve",
            new ApproveRequest());
        approverClient.DefaultRequestHeaders.Remove("Idempotency-Key");

        // Act
        var response = await _adminClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/cancel",
            new { Reason = "Should not work" });

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

    [Fact]
    public async Task Cancel_NonAdmin_Returns403()
    {
        var batchId = await UploadBatchAsync();
        var entryClient = await AuthHelper.LoginAsync(_fixture, "entry1");

        var response = await entryClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/cancel",
            new { Reason = "Should not be allowed" });

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

    [Fact]
    public async Task Cancel_CancelledBatchExcludedFromEntryQueue()
    {
        // Arrange: seed an IN_ENTRY batch and cancel it
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var entryUserId = await BatchSeedHelper.UserIdAsync(db, "entry1");

        var batchId = await UploadBatchAsync();
        await _adminClient.PatchAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/assign",
            new { EntryClerkUserId = entryUserId });

        await _adminClient.PostAsJsonAsync(
            $"/api/v1/digitization-batches/{batchId}/cancel",
            new { Reason = "Cleanup" });

        // Act
        var entryClient = await AuthHelper.LoginAsync(_fixture, "entry1");
        var response = await entryClient.GetAsync("/api/v1/work-queue/entry");
        response.StatusCode.Should().Be(HttpStatusCode.OK);

        var body = await response.Content.ReadFromJsonAsync<JsonElement>();
        var items = body.GetProperty("data").GetProperty("items");
        var batchIds = Enumerable.Range(0, items.GetArrayLength())
            .Select(i => items[i].GetProperty("batchId").GetString())
            .ToList();

        batchIds.Should().NotContain(batchId.ToString());
    }

    // ---------------------------------------------------------------
    // Sort parameter tests
    // ---------------------------------------------------------------

    [Fact]
    public async Task List_SortByCreatedAtAsc_ReturnsOldestFirst()
    {
        // Arrange: create two batches
        var batchId1 = await UploadBatchAsync();
        await Task.Delay(50);
        var batchId2 = await UploadBatchAsync();

        // Act
        var response = await _adminClient.GetAsync(
            "/api/v1/digitization-batches?sortBy=createdAt&sortDirection=asc");

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

        var body = await response.Content.ReadFromJsonAsync<JsonElement>();
        var items = body.GetProperty("data").GetProperty("items");
        var ids = Enumerable.Range(0, items.GetArrayLength())
            .Select(i => items[i].GetProperty("id").GetGuid())
            .ToList();

        var idx1 = ids.IndexOf(batchId1);
        var idx2 = ids.IndexOf(batchId2);
        idx1.Should().BeLessThan(idx2, "oldest batch should come first with asc sort");
    }

    [Fact]
    public async Task List_InvalidSortField_Returns422()
    {
        var response = await _adminClient.GetAsync(
            "/api/v1/digitization-batches?sortBy=invalidField");

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

    [Fact]
    public async Task WorkQueue_SortByCreatedAtDesc_ReturnsNewestFirst()
    {
        // Arrange: seed two pending verification batches with different ages
        using var scope = _fixture.Services.CreateScope();
        var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        var entryUserId = await BatchSeedHelper.UserIdAsync(db, "entry1");

        var olderBatch = await BatchSeedHelper.SeedBatchInPendingVerificationAsync(db, entryUserId);
        var newerBatch = await BatchSeedHelper.SeedBatchInPendingVerificationAsync(db, entryUserId);

        // Make the "older" batch actually older
        olderBatch.CreatedAt = DateTimeOffset.UtcNow.AddHours(-2);
        olderBatch.UpdatedAt = DateTimeOffset.UtcNow.AddHours(-2);
        newerBatch.CreatedAt = DateTimeOffset.UtcNow.AddMinutes(-5);
        newerBatch.UpdatedAt = DateTimeOffset.UtcNow.AddMinutes(-5);
        await db.SaveChangesAsync();

        // Act
        var verifierClient = await AuthHelper.LoginAsync(_fixture, "verifier1");
        var response = await verifierClient.GetAsync(
            "/api/v1/work-queue/verification?sortBy=createdAt&sortDirection=desc");

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

        var body = await response.Content.ReadFromJsonAsync<JsonElement>();
        var items = body.GetProperty("data").GetProperty("items");
        var batchIds = Enumerable.Range(0, items.GetArrayLength())
            .Select(i => items[i].GetProperty("batchId").GetString())
            .ToList();

        var idxNewer = batchIds.IndexOf(newerBatch.Id.ToString());
        var idxOlder = batchIds.IndexOf(olderBatch.Id.ToString());
        idxNewer.Should().BeLessThan(idxOlder, "newest batch should come first with desc sort");
    }

    // ---------------------------------------------------------------
    // Helpers
    // ---------------------------------------------------------------

    private async Task<Guid> UploadBatchAsync()
    {
        var intakeClient = await AuthHelper.LoginAsync(_fixture, "intake1");

        var fileContent = new ByteArrayContent(
            System.Text.Encoding.ASCII.GetBytes(
                $"%PDF-1.4\n%%EOF\n%test-{Guid.NewGuid()}"));
        fileContent.Headers.ContentType =
            new System.Net.Http.Headers.MediaTypeHeaderValue("application/pdf");

        var formData = new MultipartFormDataContent
        {
            { fileContent, "file", "test.pdf" },
            { new StringContent("VITALS_SHEET"), "batchType" }
        };

        var response = await intakeClient.PostAsync("/api/v1/digitization-batches", formData);
        response.EnsureSuccessStatusCode();

        var body = await response.Content.ReadFromJsonAsync<JsonElement>();
        return body.GetProperty("data").GetProperty("id").GetGuid();
    }
}
