///|
const MOONGATE_PROVIDER_STORE_FILE = "providers.json"

///|
fn moon_provider_store_path() -> String {
  @pathx.join(moon_app_config_dir(), MOONGATE_PROVIDER_STORE_FILE)
}

///|
async fn ProxyService::save_provider_store(
  self : ProxyService,
  path? : String = moon_provider_store_path(),
) -> Unit raise Error {
  let parent = @pathx.dirname(path)
  if !parent.is_empty() && parent != "." {
    @fsx.make_directory(parent, recursive=true, exists_ok=true)
  }
  atomic_write_store_file(
    path,
    self.moon_export_providers_json(include_secrets=true).stringify(indent=2) +
    "\n",
  )
}

///|
async fn ProxyService::load_provider_store(
  self : ProxyService,
  path? : String = moon_provider_store_path(),
) -> Unit raise Error {
  if !@fsx.exists_as_file(path) {
    return
  }
  let parsed = @json.parse(@fsx.read_file(path))
  match self.moon_apply_import_providers(parsed) {
    Ok(_) => ()
    Err(message) =>
      raise Failure::Failure("Invalid MoonGate provider store: \{message}")
  }
}

///|
async fn ProxyService::persist_provider_store_or_error(
  self : ProxyService,
  w : @httpx.ResponseWriter,
) -> Bool {
  self.save_provider_store() catch {
    error => {
      moongate_write_json_error(
        w,
        @status.InternalServerError,
        "moongate_provider_store_write_failed",
        "Failed to persist MoonGate providers: \{error}",
      )
      return false
    }
  }
  true
}

///|
fn provider_store_origin(row : Map[String, Json]) -> String {
  json_string_from_map(row, "origin").unwrap_or("user")
}

///|
fn ProviderRouter::all_custom_endpoints_json(self : ProviderRouter) -> Json {
  let apps : Map[String, Json] = {}
  for app_type, app_endpoints in self.custom_endpoints {
    let providers : Map[String, Json] = {}
    for provider_id in app_endpoints.keys() {
      providers[provider_id] = self.custom_endpoints_json(app_type, provider_id)
    }
    apps[app_type] = Json::object(providers)
  }
  Json::object(apps)
}

///|
fn ProviderHealthState::store_json(self : ProviderHealthState) -> Json {
  {
    "isHealthy": Json::boolean(self.is_healthy),
    "consecutiveFailures": Json::number(self.consecutive_failures.to_double()),
    "lastSuccessAt": provider_health_optional_string_json(self.last_success_at),
    "lastFailureAt": provider_health_optional_string_json(self.last_failure_at),
    "lastError": provider_health_optional_string_json(self.last_error),
    "updatedAt": Json::string(self.updated_at),
  }.to_json()
}

///|
fn ProviderRouter::provider_health_store_json(self : ProviderRouter) -> Json {
  let apps : Map[String, Json] = {}
  for app_type in moon_suite_app_types() {
    let providers : Map[String, Json] = {}
    match self.providers.get(app_type) {
      Some(app_providers) =>
        for provider_id in app_providers.keys() {
          let key = provider_health_key(app_type, provider_id)
          match self.provider_health.get(key) {
            Some(state) => providers[provider_id] = state.store_json()
            None => ()
          }
        }
      None => ()
    }
    if !providers.is_empty() {
      apps[app_type] = Json::object(providers)
    }
  }
  Json::object(apps)
}

///|
fn routing_policy_map_store_json(
  policies : Map[String, @routing.RoutingPolicyV1],
) -> Json {
  let obj : Map[String, Json] = {}
  for app_type, policy in policies {
    obj[app_type] = policy.to_json()
  }
  Json::object(obj)
}

///|
fn routing_decision_map_store_json(
  decisions : Map[String, @routing.RoutingDecisionV1],
) -> Json {
  let obj : Map[String, Json] = {}
  for app_type, decision in decisions {
    obj[app_type] = decision.to_json()
  }
  Json::object(obj)
}

///|
fn routing_smooth_map_store_json(
  states : Map[String, @routing.SmoothStateV1],
) -> Json {
  let obj : Map[String, Json] = {}
  for app_type, state in states {
    obj[app_type] = state.to_json()
  }
  Json::object(obj)
}

///|
fn routing_int_map_store_json(values : Map[String, Int]) -> Json {
  let obj : Map[String, Json] = {}
  for key, value in values {
    obj[key] = Json::number(value.to_double())
  }
  Json::object(obj)
}

///|
fn routing_int64_map_store_json(values : Map[String, Int64]) -> Json {
  let obj : Map[String, Json] = {}
  for key, value in values {
    obj[key] = Json::number(value.to_double())
  }
  Json::object(obj)
}

///|
fn ProviderRouter::routing_policy_store_json(self : ProviderRouter) -> Json {
  {
    "contractId": Json::string("moongate.routing-policy-store.v1"),
    "drafts": routing_policy_map_store_json(self.routing_policy_drafts),
    "active": routing_policy_map_store_json(self.active_routing_policies),
    "previous": routing_policy_map_store_json(self.previous_routing_policies),
    "activeSmoothState": routing_smooth_map_store_json(
      self.routing_smooth_states,
    ),
    "shadowSmoothState": routing_smooth_map_store_json(
      self.routing_shadow_smooth_states,
    ),
    "shadowCounts": routing_int_map_store_json(self.routing_shadow_counts),
    "shadowFirstObservedAtMs": routing_int64_map_store_json(
      self.routing_shadow_first_observed_at_ms,
    ),
    "lastShadowDecisions": routing_decision_map_store_json(
      self.routing_last_shadow_decisions,
    ),
    "lastActiveDecisions": routing_decision_map_store_json(
      self.routing_last_active_decisions,
    ),
    "activationReceipts": Json::array(
      self.routing_activation_receipts.map(fn(receipt) { receipt.to_json() }),
    ),
  }.to_json()
}

///|
fn routing_policy_from_store(value : Json) -> @routing.RoutingPolicyV1? {
  Some(@json.from_json(value)) catch {
    _ => None
  }
}

///|
fn routing_decision_from_store(value : Json) -> @routing.RoutingDecisionV1? {
  Some(@json.from_json(value)) catch {
    _ => None
  }
}

///|
fn routing_smooth_state_from_store(value : Json) -> @routing.SmoothStateV1? {
  Some(@json.from_json(value)) catch {
    _ => None
  }
}

///|
fn routing_receipt_from_store(
  value : Json,
) -> @routing.RoutingPolicyActivationReceiptV1? {
  Some(@json.from_json(value)) catch {
    _ => None
  }
}

///|
fn apply_import_routing_policy_map(
  target : Map[String, @routing.RoutingPolicyV1],
  value : Json,
) -> Unit {
  guard value is Object(rows) else { return }
  for app_type, policy_json in rows {
    match routing_policy_from_store(policy_json) {
      Some(policy) if policy.contract_id ==
        @routing.routing_policy_contract_v1() &&
        policy.app_type == app_type => target[app_type] = policy
      _ => ()
    }
  }
}

///|
fn apply_import_routing_decision_map(
  target : Map[String, @routing.RoutingDecisionV1],
  value : Json,
) -> Unit {
  guard value is Object(rows) else { return }
  for app_type, decision_json in rows {
    match routing_decision_from_store(decision_json) {
      Some(decision) if decision.app_type == app_type =>
        target[app_type] = decision
      _ => ()
    }
  }
}

///|
fn apply_import_routing_smooth_map(
  target : Map[String, @routing.SmoothStateV1],
  value : Json,
) -> Unit {
  guard value is Object(rows) else { return }
  for app_type, state_json in rows {
    match routing_smooth_state_from_store(state_json) {
      Some(state) => target[app_type] = state
      None => ()
    }
  }
}

///|
fn ProviderRouter::apply_import_routing_policy_store(
  self : ProviderRouter,
  value : Json,
) -> Unit {
  guard value is Object(store) else { return }
  match store.get("contractId") {
    Some(String("moongate.routing-policy-store.v1")) => ()
    _ => return
  }
  match store.get("drafts") {
    Some(value) =>
      apply_import_routing_policy_map(self.routing_policy_drafts, value)
    None => ()
  }
  match store.get("active") {
    Some(value) =>
      apply_import_routing_policy_map(self.active_routing_policies, value)
    None => ()
  }
  match store.get("previous") {
    Some(value) =>
      apply_import_routing_policy_map(self.previous_routing_policies, value)
    None => ()
  }
  match store.get("activeSmoothState") {
    Some(value) =>
      apply_import_routing_smooth_map(self.routing_smooth_states, value)
    None => ()
  }
  match store.get("shadowSmoothState") {
    Some(value) =>
      apply_import_routing_smooth_map(self.routing_shadow_smooth_states, value)
    None => ()
  }
  match store.get("shadowCounts") {
    Some(Object(counts)) =>
      for app_type, count_json in counts {
        if count_json is Number(count, ..) {
          self.routing_shadow_counts[app_type] = max_int(
            count.trunc().to_int(),
            0,
          )
        }
      }
    _ => ()
  }
  match store.get("shadowFirstObservedAtMs") {
    Some(Object(values)) =>
      for app_type, value_json in values {
        if value_json is Number(value, ..) {
          self.routing_shadow_first_observed_at_ms[app_type] = value
            .trunc()
            .to_int64()
        }
      }
    _ => ()
  }
  match store.get("lastShadowDecisions") {
    Some(value) =>
      apply_import_routing_decision_map(
        self.routing_last_shadow_decisions,
        value,
      )
    None => ()
  }
  match store.get("lastActiveDecisions") {
    Some(value) =>
      apply_import_routing_decision_map(
        self.routing_last_active_decisions,
        value,
      )
    None => ()
  }
  match store.get("activationReceipts") {
    Some(Array(receipts)) =>
      for receipt_json in receipts {
        match routing_receipt_from_store(receipt_json) {
          Some(receipt) => self.append_routing_receipt(receipt)
          None => ()
        }
      }
    _ => ()
  }
}

///|
fn provider_health_state_from_store(
  row : Map[String, Json],
) -> ProviderHealthState? {
  guard json_string_from_map(row, "updatedAt") is Some(updated_at) else {
    return None
  }
  Some({
    is_healthy: json_bool_from_map(row, "isHealthy").unwrap_or(false),
    consecutive_failures: proxy_json_int(row, "consecutiveFailures").unwrap_or(
      0,
    ),
    last_success_at: json_string_from_map(row, "lastSuccessAt"),
    last_failure_at: json_string_from_map(row, "lastFailureAt"),
    last_error: json_string_from_map(row, "lastError"),
    updated_at,
  })
}

///|
fn ProviderRouter::apply_import_provider_health(
  self : ProviderRouter,
  value : Json,
) -> Unit {
  guard value is Object(apps) else { return }
  for app_type, providers_json in apps {
    guard providers_json is Object(providers) else { continue }
    for provider_id, state_json in providers {
      guard self.provider_by_id(app_type, provider_id) is Some(_) else {
        continue
      }
      guard state_json is Object(row) else { continue }
      guard provider_health_state_from_store(row) is Some(state) else {
        continue
      }
      self.provider_health.set(
        provider_health_key(app_type, provider_id),
        state,
      )
    }
  }
}

///|
fn ProviderRouter::apply_import_custom_endpoints(
  self : ProviderRouter,
  value : Json,
) -> Unit {
  guard value is Object(apps) else { return }
  for app_type, providers_json in apps {
    guard providers_json is Object(providers) else { continue }
    for provider_id, endpoints_json in providers {
      guard self.provider_by_id(app_type, provider_id) is Some(_) else {
        continue
      }
      guard endpoints_json is Array(endpoints) else { continue }
      for endpoint in endpoints {
        guard endpoint is Object(row) else { continue }
        guard json_string_from_map(row, "url") is Some(url) else { continue }
        let added_at = proxy_json_int(row, "addedAt").unwrap_or(0)
        if !self.add_custom_endpoint(
            app_type,
            provider_id,
            url,
            now_ms=added_at,
          ) {
          continue
        }
        match proxy_json_int(row, "lastUsed") {
          Some(last_used) =>
            self.update_endpoint_last_used(
              app_type,
              provider_id,
              url,
              now_ms=last_used,
            )
          None => ()
        }
      }
    }
  }
}

///|
fn ProviderRouter::clear_imported_state(self : ProviderRouter) -> Unit {
  self.providers.clear()
  self.current_provider_ids.clear()
  self.universal_providers.clear()
  self.provider_sort_indices.clear()
  self.custom_endpoints.clear()
  self.failover_queues.clear()
  self.auto_failover_enabled.clear()
  self.max_retries.clear()
  self.circuit_breakers.clear()
  self.provider_health.clear()
  self.provider_health_observed_at_ms.clear()
  self.app_circuit_configs.clear()
  self.routing_policy_drafts.clear()
  self.active_routing_policies.clear()
  self.previous_routing_policies.clear()
  self.routing_smooth_states.clear()
  self.routing_shadow_smooth_states.clear()
  self.routing_session_bindings.clear()
  self.routing_shadow_counts.clear()
  self.routing_shadow_first_observed_at_ms.clear()
  self.routing_canary_counters.clear()
  self.routing_last_shadow_decisions.clear()
  self.routing_last_active_decisions.clear()
  self.routing_activation_receipts.clear()
  self.provider_inflight.clear()
  self.provider_quota_exhausted.clear()
  self.provider_quota_evidence_supported.clear()
}

///|
fn ProxyService::moon_apply_import_providers(
  self : ProxyService,
  providers_json : Json,
  validate_only? : Bool = false,
) -> Result[Int, String] {
  guard providers_json is Object(root) else {
    return Err("providers must be an object")
  }
  let mut has_app_data = false
  for app_type in moon_suite_app_types() {
    if root.get(app_type) is Some(Object(_)) {
      has_app_data = true
      break
    }
  }
  if !has_app_data {
    return Err("providers object contains no framework data")
  }
  for app_type in moon_suite_app_types() {
    guard root.get(app_type) is Some(Object(app_providers)) else { continue }
    for provider_id, provider_json in app_providers {
      guard provider_json is Object(_) else {
        return Err(
          "provider '\{provider_id}' in '\{app_type}' must be an object",
        )
      }
      if provider_target_from_request("", Some(provider_json), app_type) is None {
        return Err("provider '\{provider_id}' in '\{app_type}' is invalid")
      }
    }
  }
  if validate_only {
    return Ok(0)
  }
  self.provider_router.clear_imported_state()
  let mut imported = 0
  for app_type in moon_suite_app_types() {
    guard root.get(app_type) is Some(Object(app_providers)) else { continue }
    for provider_id, provider_json in app_providers {
      guard provider_json is Object(provider_obj) else { continue }
      guard provider_target_from_request("", Some(provider_json), app_type)
        is Some(parsed_target) else {
        continue
      }
      let target = {
        ..parsed_target,
        provider_id: if parsed_target.provider_id.is_empty() {
          provider_id
        } else {
          parsed_target.provider_id
        },
        origin: provider_store_origin(provider_obj),
      }
      self.provider_router.upsert_provider(target)
      imported += 1
      if json_bool_from_map(provider_obj, "isCurrent").unwrap_or(false) {
        self.provider_router.set_current_provider(app_type, target.provider_id)
      }
      if json_bool_from_map(provider_obj, "inFailoverQueue").unwrap_or(false) {
        self.provider_router.add_to_failover_queue(app_type, target.provider_id)
      }
      match proxy_json_int(provider_obj, "sortIndex") {
        Some(sort_index) =>
          ignore(
            self.provider_router.update_providers_sort_order(app_type, [
              { id: target.provider_id, sort_index },
            ]),
          )
        None => ()
      }
    }
  }
  match root.get("universalProviders") {
    Some(Object(universal)) =>
      for id, provider in universal {
        self.provider_router.universal_providers.set(id, provider)
      }
    _ => ()
  }
  match root.get("circuitBreakerConfig") {
    Some(config) =>
      match circuit_breaker_config_from_json(config) {
        Some(parsed) => self.provider_router.update_all_configs(parsed)
        None => ()
      }
    None => ()
  }
  match root.get("customEndpoints") {
    Some(endpoints) =>
      self.provider_router.apply_import_custom_endpoints(endpoints)
    None => ()
  }
  match root.get("providerHealth") {
    Some(health) => self.provider_router.apply_import_provider_health(health)
    None => ()
  }
  match root.get("autoFailover") {
    Some(Object(states)) =>
      for app_type in moon_suite_app_types() {
        match json_bool_from_map(states, app_type) {
          Some(enabled) =>
            self.provider_router.set_auto_failover(app_type, enabled)
          None => ()
        }
      }
    _ => ()
  }
  match root.get("routingPolicies") {
    Some(value) => self.provider_router.apply_import_routing_policy_store(value)
    None => ()
  }
  Ok(imported)
}