///|
/// A named environment profile.
pub struct Profile {
  name : String
  view : ConfigView
  schema : ConfigSchema?
  policy : AuditPolicy?
} derive(@debug.Debug, Eq)

///|
/// Summary row for comparing profiles.
pub struct ProfileSummary {
  name : String
  key_count : Int
  missing_required : Int
  audit_score : Int?
  sensitive_count : Int
} derive(@debug.Debug, Eq)

///|
pub fn profile(
  name : String,
  view : ConfigView,
  schema? : ConfigSchema? = None,
  policy? : AuditPolicy? = None,
) -> Profile {
  { name, view, schema, policy }
}

///|
pub fn summarize_profile(profile : Profile) -> ProfileSummary {
  let missing_required = match profile.schema {
    None => 0
    Some(spec) =>
      profile_gap_report(spec, [(profile.name, profile.view)]).length()
  }
  let audit_score = match profile.policy {
    None => None
    Some(policy) => Some(audit(profile.view, policy).score)
  }
  {
    name: profile.name,
    key_count: profile.view.values.length(),
    missing_required,
    audit_score,
    sensitive_count: sensitive_paths(profile.view).length(),
  }
}

///|
pub fn summarize_profiles(profiles : Array[Profile]) -> Array[ProfileSummary] {
  let summaries : Array[ProfileSummary] = []
  for item in profiles {
    summaries.push(summarize_profile(item))
  }
  summaries
}

///|
pub fn render_profile_summary(profiles : Array[Profile]) -> String {
  let builder = StringBuilder()
  builder.write_string(
    "| Profile | Keys | Missing Required | Audit Score | Sensitive Keys |\n",
  )
  builder.write_string("| --- | --- | --- | --- | --- |\n")
  for summary in summarize_profiles(profiles) {
    builder.write_string(
      "| " +
      summary.name +
      " | " +
      summary.key_count.to_string() +
      " | " +
      summary.missing_required.to_string() +
      " | " +
      score_text(summary.audit_score) +
      " | " +
      summary.sensitive_count.to_string() +
      " |\n",
    )
  }
  builder.to_string()
}

///|
fn score_text(score : Int?) -> String {
  match score {
    None => ""
    Some(value) => value.to_string()
  }
}

///|
/// Finds keys whose values differ across profiles.
pub fn profile_value_differences(profiles : Array[Profile]) -> Array[String] {
  let paths = collect_profile_object_paths(profiles)
  let diff_paths : Array[String] = []
  for path in paths {
    if profile_path_differs(profiles, path) {
      diff_paths.push(path)
    }
  }
  diff_paths
}

///|
fn collect_profile_object_paths(profiles : Array[Profile]) -> Array[String] {
  let paths : Array[String] = []
  for item in profiles {
    for value in item.view.values {
      let path = entry_path(value.section, value.key)
      if !profile_path_contains(paths, path) {
        paths.push(path)
      }
    }
  }
  paths
}

///|
fn profile_path_contains(paths : Array[String], target : String) -> Bool {
  for path in paths {
    if path == target {
      return true
    }
  }
  false
}

///|
fn profile_path_differs(profiles : Array[Profile], path : String) -> Bool {
  let mut seen = false
  let mut first = ""
  for item in profiles {
    match item.view.get_path(path) {
      None =>
        if seen {
          return true
        } else {
          first = ""
          seen = true
        }
      Some(value) =>
        if !seen {
          first = value
          seen = true
        } else if value != first {
          return true
        }
    }
  }
  false
}

///|
pub fn render_profile_diff_summary(profiles : Array[Profile]) -> String {
  let builder = StringBuilder()
  builder.write_string("# Profile Differences\n\n")
  let paths = profile_value_differences(profiles)
  if paths.length() == 0 {
    builder.write_string("No profile value differences.\n")
  } else {
    builder.write_string("| Path |")
    for item in profiles {
      builder.write_string(" " + item.name + " |")
    }
    builder.write_string("\n| --- |")
    for _ in profiles {
      builder.write_string(" --- |")
    }
    builder.write_string("\n")
    for path in paths {
      builder.write_string("| " + path + " |")
      for item in profiles {
        match item.view.get_path(path) {
          None => builder.write_string("  |")
          Some(value) =>
            builder.write_string(" " + redact_value(path, value) + " |")
        }
      }
      builder.write_string("\n")
    }
  }
  builder.to_string()
}

///|
/// Creates common dev/test/prod profiles from documents.
pub fn service_profiles(
  dev : ConfigDocument,
  test_doc : ConfigDocument,
  prod : ConfigDocument,
) -> Array[Profile] {
  let spec = web_service_schema()
  [
    profile(
      "dev",
      merge_layers([layer("dev", 0, dev)]),
      schema=Some(spec),
      policy=Some(development_service_policy()),
    ),
    profile(
      "test",
      merge_layers([layer("test", 0, test_doc)]),
      schema=Some(spec),
      policy=Some(development_service_policy()),
    ),
    profile(
      "prod",
      merge_layers([layer("prod", 0, prod)]),
      schema=Some(spec),
      policy=Some(production_service_policy()),
    ),
  ]
}

///|
/// Checks whether a production profile is ready enough for deployment.
pub fn deployment_ready(profile : Profile) -> Bool {
  match profile.policy {
    None => summarize_profile(profile).missing_required == 0
    Some(policy) => {
      let summary = audit(profile.view, policy)
      summary.error_count == 0 && summary.score >= 80
    }
  }
}

///|
pub fn render_deployment_check(profile : Profile) -> String {
  let builder = StringBuilder()
  builder.write_string("# Deployment Check: " + profile.name + "\n\n")
  if deployment_ready(profile) {
    builder.write_string("Status: ready\n")
  } else {
    builder.write_string("Status: blocked\n")
  }
  match profile.policy {
    None => ()
    Some(policy) =>
      builder.write_string(render_audit_summary(audit(profile.view, policy)))
  }
  builder.to_string()
}