using Microsoft.EntityFrameworkCore;

public interface ISimulationPurgeService
{
    Task<SimulationDataSummary> GetSummaryAsync(CancellationToken ct);
    Task<SimulationPurgeResult> PurgeAsync(CancellationToken ct);
}

public record SimulationDataSummary(
    int SimulatedPatients,
    int Encounters,
    int Observations,
    int Alerts,
    int ActiveRuns);

public record SimulationPurgeResult(
    int PatientsDeleted,
    int EncountersDeleted,
    int AlertsDeleted,
    int RunsCleared);

/// <summary>
/// Deletes all patients with <c>IsSimulated = true</c> and their dependents.
/// Never deletes by encounter or date range — only the simulated-patient predicate.
/// </summary>
public sealed class SimulationPurgeService : ISimulationPurgeService
{
    private readonly AppDbContext _db;
    private readonly ISimulationRunner _runner;
    private readonly IAuditService _audit;

    public SimulationPurgeService(
        AppDbContext db,
        ISimulationRunner runner,
        IAuditService audit)
    {
        _db = db;
        _runner = runner;
        _audit = audit;
    }

    public async Task<SimulationDataSummary> GetSummaryAsync(CancellationToken ct)
    {
        var patientIds = await _db.Patients
            .Where(p => p.IsSimulated)
            .Select(p => p.Id)
            .ToListAsync(ct);

        if (patientIds.Count == 0)
        {
            return new SimulationDataSummary(
                0, 0, 0, 0,
                _runner.ListRuns().Count(r =>
                    r.Status is SimulationRunStatus.Pending or SimulationRunStatus.Running));
        }

        var encounterIds = await _db.Encounters
            .Where(e => patientIds.Contains(e.PatientId))
            .Select(e => e.Id)
            .ToListAsync(ct);

        var observations = encounterIds.Count == 0
            ? 0
            : await _db.Observations.CountAsync(o => encounterIds.Contains(o.EncounterId), ct);

        var alerts = await _db.ClinicalAlerts
            .CountAsync(a => patientIds.Contains(a.PatientId), ct);

        var activeRuns = _runner.ListRuns().Count(r =>
            r.Status is SimulationRunStatus.Pending or SimulationRunStatus.Running);

        return new SimulationDataSummary(
            patientIds.Count,
            encounterIds.Count,
            observations,
            alerts,
            activeRuns);
    }

    public async Task<SimulationPurgeResult> PurgeAsync(CancellationToken ct)
    {
        if (_runner.HasActiveRuns())
            throw new ConflictException(
                "Cannot purge simulated data while runs are Pending or Running. Stop all runs first.",
                "SIMULATION_PURGE_ACTIVE_RUNS");

        await using var tx = await _db.Database.BeginTransactionAsync(ct);

        var patientIds = await _db.Patients
            .Where(p => p.IsSimulated)
            .Select(p => p.Id)
            .ToListAsync(ct);

        if (patientIds.Count == 0)
        {
            var emptyRunsCleared = await ClearSimulationRunsAsync(ct);
            await tx.CommitAsync(ct);
            _runner.ClearRegistry();

            var emptyResult = new SimulationPurgeResult(0, 0, 0, emptyRunsCleared);
            await WriteAuditAsync(emptyResult);
            return emptyResult;
        }

        var encounterIds = await _db.Encounters
            .Where(e => patientIds.Contains(e.PatientId))
            .Select(e => e.Id)
            .ToListAsync(ct);

        var alertIds = await _db.ClinicalAlerts
            .Where(a => patientIds.Contains(a.PatientId))
            .Select(a => a.Id)
            .ToListAsync(ct);

        var alertsDeleted = alertIds.Count;
        var encountersDeleted = encounterIds.Count;
        var patientsDeleted = patientIds.Count;

        // FK order — Restrict relationships require dependents first.
        if (encounterIds.Count > 0)
        {
            await _db.MedicationAdministrations
                .Where(m => encounterIds.Contains(m.EncounterId))
                .ExecuteDeleteAsync(ct);

            var bundleIds = await _db.SepsisBundles
                .Where(b => encounterIds.Contains(b.EncounterId))
                .Select(b => b.Id)
                .ToListAsync(ct);

            if (bundleIds.Count > 0)
            {
                await _db.SepsisBundleElements
                    .Where(e => bundleIds.Contains(e.BundleId))
                    .ExecuteDeleteAsync(ct);
                await _db.SepsisBundles
                    .Where(b => bundleIds.Contains(b.Id))
                    .ExecuteDeleteAsync(ct);
            }

            await _db.Orders
                .Where(o => encounterIds.Contains(o.EncounterId))
                .ExecuteDeleteAsync(ct);

            await _db.ReconciliationAlerts
                .Where(r => r.EncounterId != null && encounterIds.Contains(r.EncounterId.Value))
                .ExecuteDeleteAsync(ct);

            var encounterKeys = encounterIds.Select(id => id.ToString()).ToList();
            await _db.OutboxEvents
                .Where(o => o.PartitionKey != null && encounterKeys.Contains(o.PartitionKey))
                .ExecuteDeleteAsync(ct);

            await _db.Observations
                .Where(o => encounterIds.Contains(o.EncounterId))
                .ExecuteDeleteAsync(ct);

            await _db.News2Scores
                .Where(s => encounterIds.Contains(s.EncounterId))
                .ExecuteDeleteAsync(ct);
            await _db.SofaScores
                .Where(s => encounterIds.Contains(s.EncounterId))
                .ExecuteDeleteAsync(ct);
            await _db.GcsScores
                .Where(s => encounterIds.Contains(s.EncounterId))
                .ExecuteDeleteAsync(ct);
            await _db.QsofaEvaluations
                .Where(s => encounterIds.Contains(s.EncounterId))
                .ExecuteDeleteAsync(ct);
        }

        if (alertIds.Count > 0)
        {
            // AlertFeedback cascades with alert, but delete explicitly for clarity.
            await _db.AlertFeedbacks
                .Where(f => alertIds.Contains(f.AlertId))
                .ExecuteDeleteAsync(ct);
            await _db.ClinicalAlerts
                .Where(a => alertIds.Contains(a.Id))
                .ExecuteDeleteAsync(ct);
        }

        // Window aggregates are not patient-scoped; clear them so the next tester
        // starts with a clean quality dashboard after a ward reset.
        await _db.AlertQualityMetrics.ExecuteDeleteAsync(ct);

        await _db.PhiAccessLogs
            .Where(l => l.PatientId != null && patientIds.Contains(l.PatientId.Value))
            .ExecuteDeleteAsync(ct);

        var resourceIds = patientIds.Concat(encounterIds).ToList();
        if (resourceIds.Count > 0)
        {
            await _db.ExternalResourceIdentifiers
                .Where(x => resourceIds.Contains(x.InternalId))
                .ExecuteDeleteAsync(ct);
        }

        if (encounterIds.Count > 0)
        {
            await _db.Encounters
                .Where(e => encounterIds.Contains(e.Id))
                .ExecuteDeleteAsync(ct);
        }

        await _db.Patients
            .Where(p => patientIds.Contains(p.Id))
            .ExecuteDeleteAsync(ct);

        var runsCleared = await ClearSimulationRunsAsync(ct);

        await tx.CommitAsync(ct);
        _runner.ClearRegistry();

        var result = new SimulationPurgeResult(
            patientsDeleted, encountersDeleted, alertsDeleted, runsCleared);
        await WriteAuditAsync(result);
        return result;
    }

    private async Task<int> ClearSimulationRunsAsync(CancellationToken ct)
    {
        var count = await _db.SimulationRuns.CountAsync(ct);
        if (count > 0)
            await _db.SimulationRuns.ExecuteDeleteAsync(ct);
        return count;
    }

    private async Task WriteAuditAsync(SimulationPurgeResult result)
    {
        await _audit.WriteAsync(
            AuditAction.SimulationDataPurged,
            "SimulationData",
            Guid.NewGuid(),
            newValue: new
            {
                result.PatientsDeleted,
                result.EncountersDeleted,
                result.AlertsDeleted,
                result.RunsCleared,
            });
    }
}
