public class ReplayEngine
{
    private readonly VigilCareApiClient _client;
    private readonly ApiPoller? _poller;
    private DateTimeOffset _scenarioStartTime;

    public ReplayEngine(VigilCareApiClient client, ApiPoller? poller)
    {
        _client = client;
        _poller = poller;
    }

    public async Task<ReplayResult> RunAsync(
        ScenarioFile scenario, ReplayOptions options, CancellationToken ct = default)
    {
        var result = new ReplayResult(scenario.Scenario.Id);
        var startTime = DateTimeOffset.UtcNow;
        _scenarioStartTime = startTime;

        // --- Phase 1: Setup ---
        SimulatorConsole.Header(scenario.Scenario.Name, scenario.Scenario.Description);

        PatientResponse patient;
        EncounterResponse encounter;

        if (options.DryRun)
        {
            SimulatorConsole.DryRun("Would register patient: " +
                $"{scenario.Patient.FirstName} {scenario.Patient.LastName}");
            SimulatorConsole.DryRun("Would open encounter: " +
                $"{scenario.Encounter.Department} / {scenario.Encounter.EncounterType}");
        }
        else
        {
            patient = await _client.RegisterPatientAsync(new RegisterPatientRequest(
                scenario.Patient.FirstName,
                scenario.Patient.LastName,
                DateOnly.Parse(scenario.Patient.DateOfBirth),
                scenario.Patient.Gender));

            encounter = await _client.OpenEncounterAsync(patient.Id, new OpenEncounterRequest(
                scenario.Encounter.EncounterType,
                DepartmentMapper.ToApiDepartment(scenario.Encounter.Department),
                scenario.Encounter.AttendingPhysician,
                scenario.Encounter.RoomBed,
                scenario.Encounter.AdmissionReason));

            SimulatorConsole.Info($"Patient registered: {patient.Id} ({patient.Mrn})");
            SimulatorConsole.Info($"Encounter opened: {encounter.Id} ({encounter.Status})");
            result.PatientId = patient.Id;
            result.EncounterId = encounter.Id;
        }

        // --- Phase 2: Replay events ---
        double lastOffset = 0;
        var clusters = scenario.Events
            .GroupBy(e => e.OffsetMinutes)
            .OrderBy(g => g.Key)
            .ToList();

        foreach (var cluster in clusters)
        {
            ct.ThrowIfCancellationRequested();

            var deltaMinutes = cluster.Key - lastOffset;
            if (deltaMinutes > 0 && options.Speed > 0 && !options.DryRun)
            {
                var delayMs = (int)(deltaMinutes * 60_000 / options.Speed);
                SimulatorConsole.Wait(deltaMinutes, delayMs);
                await Task.Delay(delayMs, ct);
            }

            var simTimestamp = FormatSimTime(cluster.Key);
            var observationEvents = cluster.Where(e => e.Type == "observation").ToList();
            var otherEvents = cluster.Where(e => e.Type != "observation").ToList();

            if (observationEvents.Count > 0)
                await ReplayObservationCluster(observationEvents, simTimestamp, options, result);

            foreach (var evt in otherEvents)
            {
                switch (evt.Type)
                {
                    case "order":
                        await ReplayOrder(
                            evt, simTimestamp, options, result, scenario.Encounter.AttendingPhysician);
                        break;
                    case "medication":
                        await ReplayMedication(evt, simTimestamp, options, result);
                        break;
                    case "order_result":
                        await ReplayOrderResult(evt, simTimestamp, options, result);
                        break;
                }
            }

            lastOffset = cluster.Key;

            if (options.Poll && !options.DryRun && _poller is not null)
            {
                await Task.Delay(options.PollIntervalSeconds * 1000, ct);
                await _poller.PollAndDisplayAsync(result.EncounterId, simTimestamp);
            }
        }

        // --- Phase 3: Summary ---
        result.Duration = DateTimeOffset.UtcNow - startTime;
        SimulatorConsole.Summary(result);
        return result;
    }

    private async Task ReplayObservationCluster(
        List<ScenarioEvent> cluster, string simTime, ReplayOptions options,
        ReplayResult result)
    {
        var observations = new List<IngestObservationRequest>();
        var offsetMinutes = cluster[0].OffsetMinutes;
        var recordedAt = _scenarioStartTime.AddMinutes(offsetMinutes);

        foreach (var evt in cluster)
        {
            var code = evt.Data.GetProperty("code").GetString()!;
            var value = evt.Data.GetProperty("value").GetDecimal();
            var unit = evt.Data.GetProperty("unit").GetString()!;
            var source = evt.Data.TryGetProperty("source", out var s) ? s.GetString()! : "Manual";

            SimulatorConsole.Event(simTime, $"{code} {value} {unit}");
            result.ObservationsSent++;

            if (!options.DryRun)
            {
                observations.Add(new IngestObservationRequest(
                    code, value, unit, ToApiSource(source), recordedAt, null));
            }
        }

        if (!options.DryRun && observations.Count > 0)
            await _client.SendObservationBatchAsync(result.EncounterId, observations);
    }

    private async Task ReplayMedication(
        ScenarioEvent evt, string simTime, ReplayOptions options, ReplayResult result)
    {
        var drugName = evt.Data.GetProperty("drugName").GetString()!;
        var dose = evt.Data.GetProperty("dose").GetDecimal();
        var doseUnit = evt.Data.GetProperty("doseUnit").GetString()!;
        var route = evt.Data.GetProperty("route").GetString()!;
        var administeredBy = evt.Data.GetProperty("administeredBy").GetString()!;

        if (options.DryRun)
        {
            SimulatorConsole.DryRun($"[{simTime}] MEDICATION {drugName} {dose}{doseUnit} {route}");
            return;
        }

        var sent = await _client.TrySendMedicationAsync(result.EncounterId,
            new CreateMedicationAdministrationRequest(drugName, dose, doseUnit, route, null, administeredBy));

        if (sent)
        {
            SimulatorConsole.Event(simTime, $"MEDICATION {drugName} {dose}{doseUnit} ({route}) sent");
            result.MedicationsSent++;
        }
        else
        {
            SimulatorConsole.Warn($"[{simTime}] MEDICATION skipped — endpoint not available (Phase 15 required)");
            result.MedicationsSkipped++;
        }
    }

    private async Task ReplayOrder(
        ScenarioEvent evt, string simTime, ReplayOptions options, ReplayResult result,
        string defaultOrderedBy)
    {
        var description = evt.Data.GetProperty("description").GetString()!;
        var orderType = evt.Data.GetProperty("orderType").GetString()!;
        var orderedBy = evt.Data.TryGetProperty("orderedBy", out var ob)
            ? ob.GetString()! : defaultOrderedBy;

        if (options.DryRun)
        {
            SimulatorConsole.DryRun($"[{simTime}] ORDER {orderType}: {description}");
            return;
        }

        var placed = await _client.TryCreateOrderAsync(result.EncounterId,
            new CreateOrderRequest(orderType, description, orderedBy));

        if (placed)
        {
            SimulatorConsole.Event(simTime, $"ORDER {description} placed");
            result.OrdersPlaced++;
        }
        else
        {
            SimulatorConsole.Warn($"[{simTime}] ORDER skipped — failed to place '{description}'");
        }
    }

    private async Task ReplayOrderResult(
        ScenarioEvent evt, string simTime, ReplayOptions options, ReplayResult result)
    {
        var orderDesc = evt.Data.GetProperty("orderDescription").GetString()!;
        var resultSummary = evt.Data.TryGetProperty("resultSummary", out var rs)
            ? rs.GetString() : null;

        if (options.DryRun)
        {
            SimulatorConsole.DryRun($"[{simTime}] ORDER_RESULT {orderDesc}");
            return;
        }

        var ok = await _client.TryResultOrderAsync(
            result.EncounterId, orderDesc, resultSummary);

        if (ok)
        {
            SimulatorConsole.Event(simTime, $"ORDER_RESULT {orderDesc} → resulted");
            result.OrdersResulted++;
        }
        else
        {
            SimulatorConsole.Warn($"[{simTime}] ORDER_RESULT skipped — order '{orderDesc}' not found or already resulted");
        }
    }

    private static string FormatSimTime(double offsetMinutes)
    {
        var hours = (int)(offsetMinutes / 60);
        var mins = (int)(offsetMinutes % 60);
        return $"{hours:D2}:{mins:D2}";
    }

    private static string ToApiSource(string source) => source switch
    {
        "Device" or "DEVICE" or "device" or "monitor" => "DEVICE",
        "Manual" or "MANUAL" or "manual" => "MANUAL",
        "Lab" or "LAB" or "lab" => "LAB",
        _ => throw new InvalidOperationException($"Unknown observation source: '{source}'")
    };
}