src/lightspeed/ops/operations_kit_harness.gleam

//// Deterministic production app operations-kit harness for M40.

import gleam/int
import gleam/list
import lightspeed/ops/operations_kit

pub const snapshot_version = 1

/// M40 operations-kit scenarios.
pub type Scenario {
  IncidentRunbookCoverage
  SafeWorkflowReversibility
  TelemetryDashboardAlertContracts
  DeterministicOperationsDrills
}

/// One M40 scenario outcome.
pub type ScenarioOutcome {
  ScenarioOutcome(
    scenario: Scenario,
    passed: Bool,
    deterministic: Bool,
    signature: String,
  )
}

/// Full M40 report.
pub type Report {
  Report(
    outcomes: List(ScenarioOutcome),
    failed_scenarios: Int,
    nondeterministic_failures: Int,
  )
}

/// Run all M40 scenarios.
pub fn run_matrix() -> Report {
  let outcomes =
    [
      IncidentRunbookCoverage,
      SafeWorkflowReversibility,
      TelemetryDashboardAlertContracts,
      DeterministicOperationsDrills,
    ]
    |> list.map(run_scenario)

  Report(
    outcomes: outcomes,
    failed_scenarios: count_failed(outcomes),
    nondeterministic_failures: count_nondeterministic(outcomes),
  )
}

/// Run one M40 scenario twice and require deterministic parity.
pub fn run_scenario(scenario: Scenario) -> ScenarioOutcome {
  let #(first_passed, first_signature) = evaluate(scenario)
  let #(second_passed, second_signature) = evaluate(scenario)
  let deterministic =
    first_passed == second_passed && first_signature == second_signature
  let passed = first_passed && second_passed && deterministic

  ScenarioOutcome(
    scenario: scenario,
    passed: passed,
    deterministic: deterministic,
    signature: first_signature,
  )
}

/// Scenario label.
pub fn scenario_label(scenario: Scenario) -> String {
  case scenario {
    IncidentRunbookCoverage -> "incident_runbook_coverage"
    SafeWorkflowReversibility -> "safe_workflow_reversibility"
    TelemetryDashboardAlertContracts -> "telemetry_dashboard_alert_contracts"
    DeterministicOperationsDrills -> "deterministic_operations_drills"
  }
}

/// Stable pass/fail label.
pub fn pass_fail_label(outcome: ScenarioOutcome) -> String {
  case outcome.passed {
    True -> "pass"
    False -> "fail"
  }
}

/// Scenario signature accessor.
pub fn signature(outcome: ScenarioOutcome) -> String {
  outcome.signature
}

/// Scenario accessor.
pub fn scenario(outcome: ScenarioOutcome) -> Scenario {
  outcome.scenario
}

/// Determinism accessor.
pub fn deterministic(outcome: ScenarioOutcome) -> Bool {
  outcome.deterministic
}

/// Report outcomes accessor.
pub fn outcomes(report: Report) -> List(ScenarioOutcome) {
  report.outcomes
}

/// Failed scenario count.
pub fn failed_scenarios(report: Report) -> Int {
  report.failed_scenarios
}

/// Nondeterministic scenario count.
pub fn nondeterministic_failures(report: Report) -> Int {
  report.nondeterministic_failures
}

/// Stable report signature.
pub fn report_signature(report: Report) -> String {
  let entries =
    list.map(report.outcomes, fn(outcome) {
      scenario_label(outcome.scenario)
      <> "="
      <> pass_fail_label(outcome)
      <> ":deterministic="
      <> bool_label(outcome.deterministic)
      <> ":"
      <> outcome.signature
    })

  join_with(";", entries)
}

/// Deterministic snapshot signature for M40 fixture drift gates.
pub fn snapshot_signature() -> String {
  "m40.snapshot.v"
  <> int.to_string(snapshot_version)
  <> "|"
  <> report_signature(run_matrix())
}

/// Deterministic markdown report for M40 fixture scripts.
pub fn snapshot_report_markdown() -> String {
  let report = run_matrix()
  let failed = failed_scenarios(report)
  let nondeterministic = nondeterministic_failures(report)
  let status = case failed == 0 && nondeterministic == 0 {
    True -> "OK"
    False -> "FAIL"
  }

  "# Operations Kit Fixture Report\n\n"
  <> "snapshot_version: "
  <> int.to_string(snapshot_version)
  <> "\n"
  <> "status: "
  <> status
  <> "\n"
  <> "failed_scenarios: "
  <> int.to_string(failed)
  <> "\n"
  <> "nondeterministic_failures: "
  <> int.to_string(nondeterministic)
  <> "\n\n"
  <> "snapshot_signature: "
  <> snapshot_signature()
  <> "\n\n"
  <> "report_signature: "
  <> report_signature(report)
  <> "\n"
}

fn evaluate(scenario: Scenario) -> #(Bool, String) {
  case scenario {
    IncidentRunbookCoverage -> evaluate_incident_runbook_coverage()
    SafeWorkflowReversibility -> evaluate_safe_workflow_reversibility()
    TelemetryDashboardAlertContracts ->
      evaluate_telemetry_dashboard_alert_contracts()
    DeterministicOperationsDrills -> evaluate_deterministic_operations_drills()
  }
}

fn evaluate_incident_runbook_coverage() -> #(Bool, String) {
  let runbooks = operations_kit.canonical_runbooks()
  let signatures = runbooks |> list.map(operations_kit.runbook_signature)
  let passed = operations_kit.runbooks_cover_domains()

  #(passed, join_with(";", signatures))
}

fn evaluate_safe_workflow_reversibility() -> #(Bool, String) {
  let plans = operations_kit.workflow_plans()
  let signatures = plans |> list.map(operations_kit.workflow_plan_signature)
  let passed = operations_kit.workflow_contracts_safe()

  #(passed, join_with(";", signatures))
}

fn evaluate_telemetry_dashboard_alert_contracts() -> #(Bool, String) {
  let dashboards = operations_kit.dashboards()
  let signatures = dashboards |> list.map(operations_kit.dashboard_signature)
  let passed = operations_kit.dashboards_cover_alert_contracts()

  #(passed, join_with(";", signatures))
}

fn evaluate_deterministic_operations_drills() -> #(Bool, String) {
  let drills = operations_kit.drill_scenarios()
  let signatures = drills |> list.map(operations_kit.drill_signature)
  let passed =
    operations_kit.drills_reproducible()
    && operations_kit.operations_kit_ready()

  #(passed, join_with(";", signatures))
}

fn count_failed(outcomes: List(ScenarioOutcome)) -> Int {
  case outcomes {
    [] -> 0
    [outcome, ..rest] ->
      case outcome.passed {
        True -> count_failed(rest)
        False -> 1 + count_failed(rest)
      }
  }
}

fn count_nondeterministic(outcomes: List(ScenarioOutcome)) -> Int {
  case outcomes {
    [] -> 0
    [outcome, ..rest] ->
      case outcome.deterministic {
        True -> count_nondeterministic(rest)
        False -> 1 + count_nondeterministic(rest)
      }
  }
}

fn join_with(separator: String, values: List(String)) -> String {
  case values {
    [] -> ""
    [value] -> value
    [value, ..rest] -> value <> separator <> join_with(separator, rest)
  }
}

fn bool_label(value: Bool) -> String {
  case value {
    True -> "true"
    False -> "false"
  }
}