namespace VigilCare.Simulation;

public static class ScenarioValidator
{
    private static readonly HashSet<string> ValidCodes = new()
    {
        "HEART_RATE", "RESP_RATE", "SYSTOLIC_BP", "DIASTOLIC_BP",
        "TEMP_C", "SPO2", "AVPU", "SUPPLEMENTAL_O2",
        "WBC_K_UL", "POTASSIUM_MEQ_L", "LACTATE_MMOL_L", "GLUCOSE_MG_DL",
        // Phase 25 — GCS components
        "GCS_EYE", "GCS_VERBAL", "GCS_MOTOR",
        // Phase 26 — SOFA lab / respiratory inputs
        "PAO2_MMHG", "FIO2_PCT", "PLATELET_K_UL",
        "BILIRUBIN_MG_DL", "CREATININE_MG_DL", "URINE_OUTPUT_ML_H",
    };

    private static readonly HashSet<string> ValidSources = new()
    {
        "Manual", "Device", "Lab"
    };

    private static readonly HashSet<string> ValidDepartments = new()
    {
        "Icu", "GeneralMedicine", "Emergency", "Cardiology", "Surgery", "Pediatrics"
    };

    private static readonly HashSet<string> ValidEncounterTypes = new()
    {
        "Inpatient", "Outpatient", "Emergency"
    };

    private static readonly HashSet<string> ValidEventTypes = new()
    {
        "observation", "order", "medication", "order_result", "alert_ack"
    };

    private static readonly HashSet<string> ValidOrderTypes = new()
    {
        "Lab", "Imaging", "Medication", "Procedure"
    };

    private static readonly string[] SepsisBundleOrderPrefixes =
    [
        "SEP-1: Blood cultures",
        "SEP-1: Serum lactate",
        "SEP-1: Broad-spectrum antibiotics",
        "SEP-1: IV fluid bolus"
    ];

    public static List<string> Validate(ScenarioFile scenario)
    {
        var errors = new List<string>();

        if (string.IsNullOrWhiteSpace(scenario.Scenario.Id))
            errors.Add("scenario.id is required");
        if (string.IsNullOrWhiteSpace(scenario.Scenario.Name))
            errors.Add("scenario.name is required");
        if (string.IsNullOrWhiteSpace(scenario.Encounter.AttendingPhysician))
            errors.Add("encounter.attendingPhysician is required");
        if (!ValidDepartments.Contains(scenario.Encounter.Department))
            errors.Add($"encounter.department '{scenario.Encounter.Department}' is not valid");
        if (!ValidEncounterTypes.Contains(scenario.Encounter.EncounterType))
            errors.Add($"encounter.encounterType '{scenario.Encounter.EncounterType}' is not valid");
        if (scenario.Events.Count == 0)
            errors.Add("events array is empty");

        foreach (var group in scenario.Events
            .Where(e => e.Type == "observation")
            .GroupBy(e => e.OffsetMinutes))
        {
            if (group.Count() > 10)
                errors.Add(
                    $"offsetMinutes {group.Key}: {group.Count()} observations exceeds API batch limit of 10");
        }

        var placedOrders = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var indexedEvents = scenario.Events
            .Select((evt, i) => (evt, i))
            .OrderBy(x => x.evt.OffsetMinutes)
            .ThenBy(x => x.i);

        foreach (var (evt, i) in indexedEvents)
        {
            if (evt.OffsetMinutes < 0)
                errors.Add($"events[{i}]: offsetMinutes cannot be negative");

            if (!ValidEventTypes.Contains(evt.Type))
                errors.Add($"events[{i}]: unknown type '{evt.Type}'");

            if (evt.Type == "observation")
            {
                var code = evt.Data.TryGetProperty("code", out var codeProp) ? codeProp.GetString() : null;
                if (code is null || !ValidCodes.Contains(code))
                    errors.Add($"events[{i}]: unknown observation code '{code}'");

                var source = evt.Data.TryGetProperty("source", out var sourceProp) ? sourceProp.GetString() : null;
                if (source is not null && !ValidSources.Contains(source))
                    errors.Add($"events[{i}]: unknown observation source '{source}'");
            }

            if (evt.Type == "order")
            {
                var description = evt.Data.TryGetProperty("description", out var descProp)
                    ? descProp.GetString() : null;
                if (string.IsNullOrWhiteSpace(description))
                    errors.Add($"events[{i}]: order.description is required");

                var orderType = evt.Data.TryGetProperty("orderType", out var typeProp)
                    ? typeProp.GetString() : null;
                if (orderType is null || !ValidOrderTypes.Contains(orderType))
                    errors.Add($"events[{i}]: order.orderType '{orderType}' is not valid");

                if (!string.IsNullOrWhiteSpace(description))
                    placedOrders.Add(description);
            }

            if (evt.Type == "order_result")
            {
                var orderDescription = evt.Data.TryGetProperty("orderDescription", out var descProp)
                    ? descProp.GetString() : null;
                if (string.IsNullOrWhiteSpace(orderDescription))
                {
                    errors.Add($"events[{i}]: order_result.orderDescription is required");
                    continue;
                }

                if (!placedOrders.Contains(orderDescription)
                    && !IsSepsisBundleOrder(orderDescription)
                    && !placedOrders.Any(p => orderDescription.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
                {
                    errors.Add(
                        $"events[{i}]: order_result '{orderDescription}' has no matching prior 'order' event " +
                        "(sepsis bundle orders are auto-created when SOFA_SEPSIS or QSOFA_SCREEN fires)");
                }
            }

            if (evt.Type == "alert_ack")
            {
                var alertType = evt.Data.TryGetProperty("alertType", out var alertTypeProp)
                    ? alertTypeProp.GetString() : null;
                if (string.IsNullOrWhiteSpace(alertType))
                    errors.Add($"events[{i}]: alert_ack.alertType is required");

                var clinicianId = evt.Data.TryGetProperty("clinicianId", out var clinicianProp)
                    ? clinicianProp.GetString() : null;
                if (string.IsNullOrWhiteSpace(clinicianId))
                    errors.Add($"events[{i}]: alert_ack.clinicianId is required");
            }
        }

        return errors;
    }

    private static bool IsSepsisBundleOrder(string description) =>
        SepsisBundleOrderPrefixes.Any(p =>
            description.Equals(p, StringComparison.OrdinalIgnoreCase)
            || description.StartsWith(p, StringComparison.OrdinalIgnoreCase));
}
