///| 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()
}