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

[Collection("Integration")]
public class SimulationPurgeTests : IAsyncLifetime
{
    private readonly ApiFixture _fixture;
    private readonly HttpClient _client;
    private ISimulationRunner _runner = null!;
    private TestSimulationClientFactory _clientFactory = null!;

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

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

        _runner = _fixture.Services.GetRequiredService<ISimulationRunner>();
        _clientFactory = _fixture.SimulationClientFactory;
        _clientFactory.HangOnCreate = false;
        _clientFactory.FailOnCreate = false;

        foreach (var run in _runner.ListRuns())
            _runner.Cancel(run.RunId);
        await WaitForAsync(() => !_runner.HasActiveRuns());
        _runner.ClearRegistry();

        _client.DefaultRequestHeaders.Remove("X-Test-Role");
        _client.DefaultRequestHeaders.Remove("X-Test-User-Id");
        _client.AsNurse();
    }

    public Task DisposeAsync()
    {
        _clientFactory.HangOnCreate = false;
        _clientFactory.FailOnCreate = false;
        return Task.CompletedTask;
    }

    [Fact]
    public async Task Purge_RemovesOnlySimulatedPatients()
    {
        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            var realPatient = new Patient
            {
                Id = Guid.NewGuid(),
                Mrn = "MRN-REAL-001",
                FirstName = "Real",
                LastName = "Patient",
                DateOfBirth = new DateOnly(1975, 4, 1),
                Gender = "M",
                CreatedAt = DateTimeOffset.UtcNow,
                IsSimulated = false,
            };
            var realEncounter = new Encounter
            {
                Id = Guid.NewGuid(),
                PatientId = realPatient.Id,
                EncounterType = EncounterType.Inpatient,
                Status = EncounterStatus.Active,
                Department = Department.Icu,
                AttendingPhysician = "Dr. Real",
                AdmittedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            };
            var realObs = new Observation
            {
                Id = Guid.NewGuid(),
                EncounterId = realEncounter.Id,
                ObservationCode = "HEART_RATE",
                Value = 70,
                Unit = "bpm",
                Source = ObservationSource.Manual,
                RecordedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            };
            var realAlert = new ClinicalAlert
            {
                Id = Guid.NewGuid(),
                EncounterId = realEncounter.Id,
                PatientId = realPatient.Id,
                AlertType = AlertType.News2Warning,
                Severity = AlertSeverity.Warning,
                Details = "Real patient alert",
                Status = AlertStatus.Acknowledged,
                TriggeredAt = DateTimeOffset.UtcNow.AddMinutes(-10),
                AcknowledgedAt = DateTimeOffset.UtcNow.AddMinutes(-5),
                AcknowledgedBy = "nurse",
            };
            var realFeedback = new AlertFeedback
            {
                Id = Guid.NewGuid(),
                AlertId = realAlert.Id,
                UserId = Guid.NewGuid(),
                FeedbackType = AlertFeedbackType.Useful,
                Comment = "Keep me",
                CreatedAt = DateTimeOffset.UtcNow,
            };

            var simPatient = new Patient
            {
                Id = Guid.NewGuid(),
                Mrn = "MRN-SIM-001",
                FirstName = "Sim",
                LastName = "Patient",
                DateOfBirth = new DateOnly(1980, 1, 1),
                Gender = "F",
                CreatedAt = DateTimeOffset.UtcNow,
                IsSimulated = true,
            };
            var simEncounter = new Encounter
            {
                Id = Guid.NewGuid(),
                PatientId = simPatient.Id,
                EncounterType = EncounterType.Inpatient,
                Status = EncounterStatus.Active,
                Department = Department.GeneralMedicine,
                AttendingPhysician = "Dr. Sim",
                AdmittedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            };
            var simObs = new Observation
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                ObservationCode = "HEART_RATE",
                Value = 110,
                Unit = "bpm",
                Source = ObservationSource.Manual,
                RecordedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            };
            var simAlert = new ClinicalAlert
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                PatientId = simPatient.Id,
                AlertType = AlertType.News2Emergency,
                Severity = AlertSeverity.Critical,
                Details = "Simulated alert",
                Status = AlertStatus.Open,
                TriggeredAt = DateTimeOffset.UtcNow,
            };

            db.Patients.AddRange(realPatient, simPatient);
            db.Encounters.AddRange(realEncounter, simEncounter);
            db.Observations.AddRange(realObs, simObs);
            db.ClinicalAlerts.AddRange(realAlert, simAlert);
            db.AlertFeedbacks.Add(realFeedback);
            await db.SaveChangesAsync();
        }

        var resp = await _client.DeleteAsync("/api/v1/simulation/data");
        resp.StatusCode.Should().Be(HttpStatusCode.OK);

        var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
        var data = body.GetProperty("data");
        data.GetProperty("patientsDeleted").GetInt32().Should().Be(1);
        data.GetProperty("encountersDeleted").GetInt32().Should().Be(1);
        data.GetProperty("alertsDeleted").GetInt32().Should().Be(1);

        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            (await db.Patients.AnyAsync(p => p.Mrn == "MRN-SIM-001")).Should().BeFalse();

            var real = await db.Patients.SingleAsync(p => p.Mrn == "MRN-REAL-001");
            real.IsSimulated.Should().BeFalse();

            (await db.Observations.CountAsync(o =>
                db.Encounters.Any(e => e.Id == o.EncounterId && e.PatientId == real.Id)))
                .Should().Be(1);
            (await db.ClinicalAlerts.CountAsync(a => a.PatientId == real.Id)).Should().Be(1);
            (await db.AlertFeedbacks.CountAsync()).Should().Be(1);
            (await db.Patients.CountAsync(p => p.IsSimulated)).Should().Be(0);
        }
    }

    [Fact]
    public async Task Purge_CascadesDependentRows()
    {
        Guid simPatientId;
        Guid simEncounterId;

        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();

            var simPatient = new Patient
            {
                Id = Guid.NewGuid(),
                Mrn = "MRN-SIM-CASCADE",
                FirstName = "Cascade",
                LastName = "Sim",
                DateOfBirth = new DateOnly(1982, 2, 2),
                Gender = "M",
                CreatedAt = DateTimeOffset.UtcNow,
                IsSimulated = true,
            };
            var simEncounter = new Encounter
            {
                Id = Guid.NewGuid(),
                PatientId = simPatient.Id,
                EncounterType = EncounterType.Inpatient,
                Status = EncounterStatus.Active,
                Department = Department.Icu,
                AttendingPhysician = "Dr. Cascade",
                AdmittedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            };
            simPatientId = simPatient.Id;
            simEncounterId = simEncounter.Id;

            db.Patients.Add(simPatient);
            db.Encounters.Add(simEncounter);
            db.Observations.Add(new Observation
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                ObservationCode = "HEART_RATE",
                Value = 120,
                Unit = "bpm",
                Source = ObservationSource.Manual,
                RecordedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            });
            db.News2Scores.Add(new News2Score
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                PatientId = simPatient.Id,
                TotalScore = 5,
                RiskLevel = "MEDIUM",
                CalculatedAt = DateTimeOffset.UtcNow,
            });
            db.GcsScores.Add(new GcsScore
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                PatientId = simPatient.Id,
                EyeScore = 4,
                VerbalScore = 5,
                MotorScore = 6,
                TotalScore = 15,
                Classification = "MILD",
                CalculatedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            });
            db.SofaScores.Add(new SofaScore
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                PatientId = simPatient.Id,
                TotalScore = 2,
                CalculatedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            });
            db.QsofaEvaluations.Add(new QsofaEvaluation
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                PatientId = simPatient.Id,
                ActiveCriteria = 1,
                EvaluatedAt = DateTimeOffset.UtcNow,
                CreatedAt = DateTimeOffset.UtcNow,
            });
            var alert = new ClinicalAlert
            {
                Id = Guid.NewGuid(),
                EncounterId = simEncounter.Id,
                PatientId = simPatient.Id,
                AlertType = AlertType.News2Warning,
                Severity = AlertSeverity.Warning,
                Details = "Cascade alert",
                Status = AlertStatus.Acknowledged,
                TriggeredAt = DateTimeOffset.UtcNow,
                AcknowledgedAt = DateTimeOffset.UtcNow,
                AcknowledgedBy = "nurse",
            };
            db.ClinicalAlerts.Add(alert);
            db.AlertFeedbacks.Add(new AlertFeedback
            {
                Id = Guid.NewGuid(),
                AlertId = alert.Id,
                UserId = Guid.NewGuid(),
                FeedbackType = AlertFeedbackType.Useful,
                CreatedAt = DateTimeOffset.UtcNow,
            });
            db.PhiAccessLogs.Add(new PhiAccessLog
            {
                Id = Guid.NewGuid(),
                PatientId = simPatient.Id,
                AccessType = PhiAccessType.View,
                AccessedAt = DateTimeOffset.UtcNow,
                UserId = Guid.NewGuid(),
                UserDisplayName = "nurse",
                ResourcePath = "/api/v1/patients/" + simPatient.Id,
            });
            await db.SaveChangesAsync();
        }

        var resp = await _client.DeleteAsync("/api/v1/simulation/data");
        resp.StatusCode.Should().Be(HttpStatusCode.OK);

        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            (await db.Patients.AnyAsync(p => p.Id == simPatientId)).Should().BeFalse();
            (await db.Encounters.AnyAsync(e => e.Id == simEncounterId)).Should().BeFalse();
            (await db.Observations.AnyAsync(o => o.EncounterId == simEncounterId)).Should().BeFalse();
            (await db.News2Scores.AnyAsync(s => s.EncounterId == simEncounterId)).Should().BeFalse();
            (await db.GcsScores.AnyAsync(s => s.EncounterId == simEncounterId)).Should().BeFalse();
            (await db.SofaScores.AnyAsync(s => s.EncounterId == simEncounterId)).Should().BeFalse();
            (await db.QsofaEvaluations.AnyAsync(s => s.EncounterId == simEncounterId)).Should().BeFalse();
            (await db.ClinicalAlerts.AnyAsync(a => a.PatientId == simPatientId)).Should().BeFalse();
            (await db.AlertFeedbacks.CountAsync()).Should().Be(0);
            (await db.PhiAccessLogs.AnyAsync(l => l.PatientId == simPatientId)).Should().BeFalse();
        }
    }

    [Fact]
    public async Task Purge_WithActiveRun_Returns409()
    {
        _clientFactory.HangOnCreate = true;
        var state = await _runner.StartAsync("minimal-sim-01", 60, "tester", CancellationToken.None);

        var resp = await _client.DeleteAsync("/api/v1/simulation/data");
        resp.StatusCode.Should().Be(HttpStatusCode.Conflict);

        var body = await resp.Content.ReadFromJsonAsync<JsonElement>();
        body.GetProperty("error").GetProperty("code").GetString()
            .Should().Be("SIMULATION_PURGE_ACTIVE_RUNS");

        _runner.Cancel(state.RunId);
        await WaitForAsync(() => !_runner.HasActiveRuns());
    }

    [Fact]
    public async Task Purge_WritesAuditLog()
    {
        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            db.Patients.Add(new Patient
            {
                Id = Guid.NewGuid(),
                Mrn = "MRN-SIM-AUDIT",
                FirstName = "Sim",
                LastName = "Audit",
                DateOfBirth = new DateOnly(1990, 1, 1),
                Gender = "M",
                CreatedAt = DateTimeOffset.UtcNow,
                IsSimulated = true,
            });
            await db.SaveChangesAsync();
        }

        var resp = await _client.DeleteAsync("/api/v1/simulation/data");
        resp.StatusCode.Should().Be(HttpStatusCode.OK);

        using (var scope = _fixture.Services.CreateScope())
        {
            var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            var audit = await db.ClinicalAuditLogs
                .SingleAsync(l => l.Action == AuditAction.SimulationDataPurged);
            audit.NewValueJson.Should().Contain("PatientsDeleted");
        }
    }

    [Fact]
    public async Task Purge_WhenDisabled_Returns404()
    {
        using var factory = _fixture.WithWebHostBuilder(builder =>
        {
            builder.ConfigureAppConfiguration((_, config) =>
            {
                config.AddInMemoryCollection(new Dictionary<string, string?>
                {
                    ["Simulation:Enabled"] = "false",
                });
            });
        });

        var client = factory.CreateClient();
        client.AsNurse();

        var resp = await client.DeleteAsync("/api/v1/simulation/data");
        resp.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }

    private static async Task WaitForAsync(Func<bool> condition, TimeSpan? timeout = null)
    {
        var deadline = DateTimeOffset.UtcNow + (timeout ?? TimeSpan.FromSeconds(15));
        while (DateTimeOffset.UtcNow < deadline)
        {
            if (condition())
                return;
            await Task.Delay(25);
        }

        throw new TimeoutException("Condition was not met within the timeout.");
    }
}
