namespace VigilCare.Simulation;

public static class ExpectedOutcomeValidator
{
  private static readonly TimeSpan AsyncSettleDelay = TimeSpan.FromSeconds(8);

  public static async Task<List<string>> ValidateAsync(
      VigilCareApiClient client,
      Guid encounterId,
      ScenarioFile scenario,
      CancellationToken ct = default)
  {
    if (scenario.ExpectedOutcomes is not { Count: > 0 })
      return [];

    await Task.Delay(AsyncSettleDelay, ct);

    var failures = new List<string>();
    var alerts = await client.GetAlertsAsync(encounterId);

    foreach (var outcome in scenario.ExpectedOutcomes)
    {
      switch (outcome.Type)
      {
        case "alert":
          failures.AddRange(ValidateAlertOutcome(outcome, alerts, scenario.Scenario.Id));
          break;
        case "score":
          failures.AddRange(await ValidateScoreOutcomeAsync(client, encounterId, outcome, scenario.Scenario.Id, ct));
          break;
        case "bundle":
          failures.AddRange(await ValidateBundleOutcomeAsync(client, encounterId, outcome, scenario.Scenario.Id, ct));
          break;
        default:
          failures.Add($"[{scenario.Scenario.Id}] Unknown expected outcome type '{outcome.Type}'");
          break;
      }
    }

    return failures;
  }

  private static IEnumerable<string> ValidateAlertOutcome(
      ExpectedOutcome outcome, List<AlertResponse> alerts, string scenarioId)
  {
    if (string.IsNullOrWhiteSpace(outcome.AlertType))
    {
      yield return $"[{scenarioId}] Alert outcome at T+{outcome.AfterOffsetMinutes}m missing alertType";
      yield break;
    }

    var matching = alerts
        .Where(a => string.Equals(a.AlertType, outcome.AlertType, StringComparison.OrdinalIgnoreCase))
        .ToList();

    if (matching.Count == 0)
    {
      yield return
          $"[{scenarioId}] Expected alert {outcome.AlertType} at T+{outcome.AfterOffsetMinutes}m — not found " +
          $"(description: {outcome.Description ?? "n/a"})";
      yield break;
    }

    if (!string.IsNullOrWhiteSpace(outcome.NarrativeContains))
    {
      var withNarrative = matching.FirstOrDefault(a =>
          a.Explanation?.NarrativeSummary?.Contains(
              outcome.NarrativeContains, StringComparison.OrdinalIgnoreCase) == true);

      if (withNarrative is null)
      {
        var summaries = matching
            .Select(a => a.Explanation?.NarrativeSummary ?? a.Details)
            .Take(2);
        yield return
            $"[{scenarioId}] Alert {outcome.AlertType} at T+{outcome.AfterOffsetMinutes}m — " +
            $"narrative does not contain '{outcome.NarrativeContains}' " +
            $"(got: {string.Join(" | ", summaries)})";
      }
    }
  }

  private static async Task<IEnumerable<string>> ValidateScoreOutcomeAsync(
      VigilCareApiClient client,
      Guid encounterId,
      ExpectedOutcome outcome,
      string scenarioId,
      CancellationToken ct)
  {
    if (string.IsNullOrWhiteSpace(outcome.ScoreType))
      return [$"[{scenarioId}] Score outcome at T+{outcome.AfterOffsetMinutes}m missing scoreType"];

    if (!outcome.ExpectedMinimum.HasValue)
      return [$"[{scenarioId}] Score outcome at T+{outcome.AfterOffsetMinutes}m missing expectedMinimum"];

    var actual = await GetScoreAsync(client, encounterId, outcome.ScoreType, ct);
    if (actual is null)
    {
      return
      [
        $"[{scenarioId}] Expected {outcome.ScoreType} ≥ {outcome.ExpectedMinimum} at T+{outcome.AfterOffsetMinutes}m — score not available"
      ];
    }

    if (actual.Value < outcome.ExpectedMinimum.Value)
    {
      return
      [
        $"[{scenarioId}] Expected {outcome.ScoreType} ≥ {outcome.ExpectedMinimum} at T+{outcome.AfterOffsetMinutes}m — got {actual.Value} " +
        $"(description: {outcome.Description ?? "n/a"})"
      ];
    }

    return [];
  }

  private static async Task<IEnumerable<string>> ValidateBundleOutcomeAsync(
      VigilCareApiClient client,
      Guid encounterId,
      ExpectedOutcome outcome,
      string scenarioId,
      CancellationToken ct)
  {
    var bundle = await client.GetSepsisBundleAsync(encounterId);
    if (bundle is null)
    {
      return
      [
        $"[{scenarioId}] Expected sepsis bundle at T+{outcome.AfterOffsetMinutes}m — bundle not found " +
        $"(description: {outcome.Description ?? "n/a"})"
      ];
    }

    return [];
  }

  private static async Task<int?> GetScoreAsync(
      VigilCareApiClient client,
      Guid encounterId,
      string scoreType,
      CancellationToken ct)
  {
    return scoreType.ToUpperInvariant() switch
    {
      "NEWS2" => (await client.GetCurrentNews2Async(encounterId))?.TotalScore,
      "GCS" => (await client.GetCurrentGcsAsync(encounterId))?.TotalScore,
      "SOFA" => (await client.GetCurrentSofaAsync(encounterId))?.TotalScore,
      "QSOFA" => (await client.GetCurrentQsofaAsync(encounterId))?.ActiveCriteria,
      _ => null
    };
  }
}
