//! HTTP management adapter for [`wasmeld_runtime`].
//!
//! The console persists component artifacts and manifests on the local
//! filesystem. Service metadata, invocation metrics, and events are persisted
//! to libSQL through Toasty. It also hosts an immutable filesystem-backed WIT
//! package Registry. Actor memory remains process-local and is never presented
//! as durable state.
//!
//! # Ownership model
//!
//! * [`Console`] owns the in-process Runtime and serializes control-plane
//!   mutations with a lifecycle mutex.
//! * libSQL stores desired service/deployment state and bounded telemetry.
//! * Component and WIT bytes are immutable filesystem artifacts addressed by
//!   identity and digest.
//! * On restart, Actors are recreated from persisted manifests; their linear
//!   memory is intentionally reset.
//!
//! # Example
//!
//! ```no_run
//! use wasmeld_console::{Console, ConsoleConfig};
//!
//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
//! let console = Console::new(ConsoleConfig::default()).await?;
//! for service in console.services()? {
//!     println!("{}@{}: {:?}", service.id, service.revision, service.status);
//! }
//! # Ok(())
//! # }
//! ```

#![warn(missing_docs)]

mod api;
mod invocation_persistence;
mod kv_backend;
mod persistence;
mod web;
mod wit_registry;

use std::{
    collections::{BTreeMap, VecDeque},
    fs, io,
    path::{Path, PathBuf},
    sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard},
    time::{Instant, SystemTime, UNIX_EPOCH},
};

use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use wasmeld_package::{
    ComponentPackage, PackageError, read_package, wit_package::WitPackageMetadata,
};
use wasmeld_runtime::{
    CapabilityDescriptor, ResourceLimits, Runtime, RuntimeConfig, RuntimeError, ServiceKey,
    ServiceManifest,
};

pub use api::{app, gateway_app};
use invocation_persistence::InvocationPersistence;
use kv_backend::ToastyKvBackend;
use persistence::{InvocationUpdate, Persistence, StoredDeployment, StoredEvent, StoredService};
use wit_registry::WitRegistry;
pub use wit_registry::WitRegistryError;

const MAX_EVENTS: usize = 256;
const DEFAULT_MAX_ARTIFACT_BYTES: usize = 64 * 1024 * 1024;
const DEFAULT_MAX_WIT_PACKAGE_BYTES: usize = 4 * 1024 * 1024;
type PersistenceSnapshot = (Vec<StoredService>, Vec<StoredEvent>, Vec<StoredDeployment>);

/// Filesystem, persistence, upload, and Runtime policy used by [`Console`].
#[derive(Clone, Debug)]
pub struct ConsoleConfig {
    /// Directory for registered Component binaries and generated manifests.
    pub artifact_dir: PathBuf,
    /// Directory for immutable binary WIT Registry artifacts.
    pub wit_registry_dir: PathBuf,
    /// Local libSQL database file.
    pub database_path: PathBuf,
    /// Maximum accepted `.wasmpkg` and uncompressed Component size.
    pub max_artifact_bytes: usize,
    /// Maximum accepted binary WIT package size.
    pub max_wit_package_bytes: usize,
    /// Platform-owned limits injected into every registered service.
    pub registration_limits: ResourceLimits,
    /// Wasmtime Engine and epoch settings.
    pub runtime: RuntimeConfig,
}

impl Default for ConsoleConfig {
    fn default() -> Self {
        Self {
            artifact_dir: PathBuf::from("var/wasmeld/components"),
            wit_registry_dir: PathBuf::from("var/wasmeld/wit-packages"),
            database_path: PathBuf::from("var/wasmeld/console.db"),
            max_artifact_bytes: DEFAULT_MAX_ARTIFACT_BYTES,
            max_wit_package_bytes: DEFAULT_MAX_WIT_PACKAGE_BYTES,
            registration_limits: ResourceLimits::default(),
            runtime: RuntimeConfig::default(),
        }
    }
}

/// Management state that owns one optional Runtime and its durable control data.
///
/// Runtime stop/start operations replace the in-process Wasmtime state while
/// registered service metadata, artifacts, metrics, and events remain managed
/// by the Console.
pub struct Console {
    // Lock order for lifecycle mutations is `lifecycle` -> `runtime` ->
    // `state`. Invocation does not take `lifecycle`; it briefly reads Runtime
    // and then updates state after the Actor call. Keep new code on this order
    // to avoid deadlocks between HTTP control operations.
    runtime: RwLock<Option<Runtime>>,
    runtime_config: RuntimeConfig,
    artifact_dir: PathBuf,
    wit_registry: WitRegistry,
    max_artifact_bytes: usize,
    registration_limits: ResourceLimits,
    lifecycle: Mutex<()>,
    state: Mutex<ConsoleState>,
    persistence: Persistence,
    persistence_sync: Arc<tokio::sync::Mutex<()>>,
    invocation_persistence: InvocationPersistence,
}

struct ConsoleState {
    runtime_started_at: Option<Instant>,
    next_event_id: u64,
    services: BTreeMap<ServiceKey, ServiceRecord>,
    deployments: BTreeMap<String, DeploymentRecord>,
    events: VecDeque<EventView>,
}

#[derive(Clone)]
struct ServiceRecord {
    manifest: ServiceManifest,
    capabilities: Vec<CapabilityView>,
    status: ServiceStatus,
    updated_at_ms: u64,
    calls: u64,
    errors: u64,
    last_latency_ms: Option<u64>,
}

#[derive(Clone)]
struct DeploymentRecord {
    active_revision: String,
    updated_at_ms: u64,
}

/// Lifecycle status exposed for one registered service version.
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ServiceStatus {
    /// Actor is accepting invocations for this revision.
    Running,
    /// Revision is registered but has no active Actor.
    Stopped,
    /// Its most recent Actor trapped and must be started again.
    Faulted,
}

/// API projection of one public service deployment.
#[derive(Clone, Debug, Serialize)]
pub struct DeploymentView {
    /// Stable public service ID used by the Gateway route.
    pub service_id: String,
    /// Immutable revision currently selected for the service.
    pub active_revision: String,
    /// Current lifecycle status of the selected revision.
    pub status: ServiceStatus,
    /// Unix timestamp of the most recent deployment change.
    pub updated_at_ms: u64,
}

/// Exact versioned host interface imported by a registered Component.
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CapabilityView {
    /// Fully qualified versioned WIT interface.
    pub interface: String,
    /// WIT package identity without the version suffix.
    pub package: String,
    /// Interface name inside the package.
    pub name: String,
    /// Exact semantic version linked by the Runtime.
    pub version: String,
}

impl From<CapabilityDescriptor> for CapabilityView {
    fn from(capability: CapabilityDescriptor) -> Self {
        Self {
            interface: capability.interface().to_owned(),
            package: capability.package().to_owned(),
            name: capability.name().to_owned(),
            version: capability.version().to_owned(),
        }
    }
}

/// API projection of a registered service and its current metrics.
#[derive(Clone, Debug, Serialize)]
pub struct ServiceView {
    /// Stable service ID.
    pub id: String,
    /// Immutable Component revision.
    pub revision: String,
    /// Canonical path of the stored Component artifact.
    pub artifact: String,
    /// Fully versioned world declared by the package.
    pub world: String,
    /// Exact Host interfaces discovered from Component imports.
    pub capabilities: Vec<CapabilityView>,
    /// Current Actor status for this revision.
    pub status: ServiceStatus,
    /// Unix timestamp of the most recent lifecycle change.
    pub updated_at_ms: u64,
    /// Platform limits injected during registration.
    pub limits: ResourceLimits,
    /// Persisted number of completed invocation attempts.
    pub calls: u64,
    /// Persisted number of failed invocation attempts.
    pub errors: u64,
    /// Latency of the most recently persisted invocation.
    pub last_latency_ms: Option<u64>,
}

impl ServiceRecord {
    fn view(&self) -> ServiceView {
        ServiceView {
            id: self.manifest.id.clone(),
            revision: self.manifest.revision.clone(),
            artifact: self.manifest.component.clone(),
            world: self.manifest.world.clone(),
            capabilities: self.capabilities.clone(),
            status: self.status,
            updated_at_ms: self.updated_at_ms,
            limits: self.manifest.limits.clone(),
            calls: self.calls,
            errors: self.errors,
            last_latency_ms: self.last_latency_ms,
        }
    }
}

/// One persisted Console lifecycle or invocation event.
#[derive(Clone, Debug, Serialize)]
pub struct EventView {
    /// Process-independent monotonically increasing event identity.
    pub id: u64,
    /// Event creation time as Unix milliseconds.
    pub timestamp_ms: u64,
    /// Stable category used by API consumers.
    pub kind: EventKind,
    /// Related service ID, or `None` for Runtime-wide events.
    pub service_id: Option<String>,
    /// Related immutable revision when the event targets one version.
    pub revision: Option<String>,
    /// Human-readable operational detail; not a machine protocol.
    pub message: String,
}

/// Event category serialized by the management API.
#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum EventKind {
    /// A Component package was registered.
    Registered,
    /// An inactive revision and its artifacts were removed.
    Unregistered,
    /// A public deployment switched to a revision.
    Deployed,
    /// Runtime or Actor startup completed.
    Started,
    /// Runtime or Actor shutdown completed.
    Stopped,
    /// One data-plane or management invocation completed.
    Invoked,
    /// A lifecycle or invocation operation failed.
    Failed,
}

impl EventKind {
    fn as_database_value(self) -> &'static str {
        match self {
            Self::Registered => "registered",
            Self::Unregistered => "unregistered",
            Self::Deployed => "deployed",
            Self::Started => "started",
            Self::Stopped => "stopped",
            Self::Invoked => "invoked",
            Self::Failed => "failed",
        }
    }

    fn from_database_value(value: &str) -> Result<Self, ConsoleError> {
        match value {
            "registered" => Ok(Self::Registered),
            "unregistered" => Ok(Self::Unregistered),
            "deployed" => Ok(Self::Deployed),
            "started" => Ok(Self::Started),
            "stopped" => Ok(Self::Stopped),
            "invoked" => Ok(Self::Invoked),
            "failed" => Ok(Self::Failed),
            _ => Err(ConsoleError::InvalidDatabaseData(format!(
                "unsupported event kind {value:?}"
            ))),
        }
    }
}

impl TryFrom<StoredEvent> for EventView {
    type Error = ConsoleError;

    fn try_from(event: StoredEvent) -> Result<Self, Self::Error> {
        Ok(Self {
            id: event.id,
            timestamp_ms: event.timestamp_ms,
            kind: EventKind::from_database_value(&event.kind)?,
            service_id: event.service_id,
            revision: event.revision,
            message: event.message,
        })
    }
}

impl From<EventView> for StoredEvent {
    fn from(event: EventView) -> Self {
        Self {
            id: event.id,
            timestamp_ms: event.timestamp_ms,
            kind: event.kind.as_database_value().to_owned(),
            service_id: event.service_id,
            revision: event.revision,
            message: event.message,
        }
    }
}

#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct StartRequest {
    init_config_base64: Option<String>,
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct InvokeRequest {
    input_base64: String,
}

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct ActivateDeploymentRequest {
    revision: String,
    init_config_base64: Option<String>,
}

#[derive(Debug, Serialize)]
struct ServiceList {
    services: Vec<ServiceView>,
}

#[derive(Debug, Serialize)]
struct DeploymentList {
    deployments: Vec<DeploymentView>,
}

#[derive(Debug, Serialize)]
struct EventList {
    events: Vec<EventView>,
}

#[derive(Debug, Serialize)]
struct WitPackageList {
    packages: Vec<WitPackageMetadata>,
}

#[derive(Debug, Serialize)]
struct RuntimeView {
    status: RuntimeStatus,
    uptime_ms: u64,
    managed_services: usize,
    registered_services: usize,
    running_services: usize,
}

#[derive(Clone, Copy, Debug, Serialize)]
#[serde(rename_all = "snake_case")]
enum RuntimeStatus {
    Running,
    Stopped,
}

#[derive(Debug, Serialize)]
struct InvokeResponse {
    output_base64: String,
    output_bytes: usize,
    latency_ms: u64,
}

struct InvocationResult {
    output: Vec<u8>,
    latency_ms: u64,
}

struct GatewayInvocation {
    revision: String,
    result: InvocationResult,
}

/// Errors raised by Console state, persistence, Registry, or Runtime operations.
#[derive(Debug, Error)]
pub enum ConsoleError {
    /// API input failed semantic validation.
    #[error("{0}")]
    InvalidRequest(String),

    /// The requested registered service revision does not exist.
    #[error("service revision {0} is not managed by Wasmeld")]
    ServiceNotFound(ServiceKey),

    /// The public service has no selected revision.
    #[error("service {0} has no active deployment")]
    DeploymentNotFound(String),

    /// Unregister was attempted on the revision currently serving traffic.
    #[error("service revision {0} is the active deployment")]
    ActiveDeployment(ServiceKey),

    /// An operation requiring Wasmtime was attempted while Runtime is stopped.
    #[error("Wasmeld Runtime is not running")]
    RuntimeNotRunning,

    /// Uploaded package bytes exceed the configured control-plane limit.
    #[error("artifact exceeds the {limit}-byte limit")]
    ArtifactTooLarge {
        /// Maximum accepted upload bytes.
        limit: usize,
    },

    /// Component package decoding or integrity validation failed.
    #[error(transparent)]
    Package(#[from] PackageError),

    /// WIT Registry storage or integrity validation failed.
    #[error(transparent)]
    WitRegistry(#[from] WitRegistryError),

    /// Component artifact or manifest filesystem access failed.
    #[error("failed to access {path}: {source}")]
    Storage {
        /// Path involved in the failed operation.
        path: PathBuf,
        /// Underlying filesystem failure.
        #[source]
        source: io::Error,
    },

    /// A generated Runtime manifest could not be encoded.
    #[error("failed to serialize manifest: {0}")]
    ManifestSerialize(#[from] toml::ser::Error),

    /// Toasty or libSQL control-state persistence failed.
    #[error("database operation failed: {0}")]
    Database(#[from] toasty::Error),

    /// The synchronous-to-async KV worker thread could not start.
    #[error("failed to start persistent KV backend: {0}")]
    KvBackendStart(#[source] io::Error),

    /// Persisted rows contain an unknown enum, invalid key, or invalid revision.
    #[error("database contains invalid console data: {0}")]
    InvalidDatabaseData(String),

    /// Component compilation, Actor lifecycle, or invocation failed.
    #[error(transparent)]
    Runtime(#[from] RuntimeError),

    /// A blocking Runtime operation panicked or was cancelled.
    #[error("blocking runtime task failed: {0}")]
    Join(#[from] tokio::task::JoinError),

    /// An internal lifecycle, state, or Runtime lock was poisoned.
    #[error("console state lock was poisoned")]
    LockPoisoned,
}

impl Console {
    /// Opens durable stores, restores control data, and starts a fresh Runtime.
    ///
    /// Actors selected by durable deployments are recreated with empty init
    /// configuration. Their previous Store and linear memory are not restored.
    pub async fn new(config: ConsoleConfig) -> Result<Self, ConsoleError> {
        config.registration_limits.validate()?;
        fs::create_dir_all(&config.artifact_dir).map_err(|source| ConsoleError::Storage {
            path: config.artifact_dir.clone(),
            source,
        })?;
        let artifact_dir =
            fs::canonicalize(&config.artifact_dir).map_err(|source| ConsoleError::Storage {
                path: config.artifact_dir.clone(),
                source,
            })?;
        if let Some(parent) = config.database_path.parent() {
            fs::create_dir_all(parent).map_err(|source| ConsoleError::Storage {
                path: parent.to_path_buf(),
                source,
            })?;
        }
        let persistence = Persistence::open(&config.database_path).await?;
        let mut runtime_config = config.runtime;
        if runtime_config.component_cache_dir.is_none() {
            runtime_config.component_cache_dir = Some(
                artifact_dir
                    .parent()
                    .unwrap_or(&artifact_dir)
                    .join("component-cache"),
            );
        }
        if runtime_config.kv_backend.is_none() {
            runtime_config.kv_backend = Some(Arc::new(
                ToastyKvBackend::start(persistence.clone())
                    .map_err(ConsoleError::KvBackendStart)?,
            ));
        }
        let wit_registry =
            WitRegistry::open(&config.wit_registry_dir, config.max_wit_package_bytes)?;
        let (stored_services, mut stored_events, stored_deployments) = persistence.load().await?;
        stored_events.sort_unstable_by_key(|event| std::cmp::Reverse(event.id));
        stored_events.truncate(MAX_EVENTS);
        let next_event_id = stored_events
            .first()
            .map(|event| event.id.saturating_add(1))
            .unwrap_or(1);
        let events = stored_events
            .into_iter()
            .map(EventView::try_from)
            .collect::<Result<VecDeque<_>, _>>()?;

        let runtime = Runtime::new(runtime_config.clone())?;
        let persistence_sync = Arc::new(tokio::sync::Mutex::new(()));
        let invocation_persistence =
            InvocationPersistence::start(persistence.clone(), Arc::clone(&persistence_sync));
        let console = Self {
            runtime: RwLock::new(Some(runtime)),
            runtime_config,
            artifact_dir,
            wit_registry,
            max_artifact_bytes: config.max_artifact_bytes,
            registration_limits: config.registration_limits,
            lifecycle: Mutex::new(()),
            state: Mutex::new(ConsoleState {
                runtime_started_at: Some(Instant::now()),
                next_event_id,
                services: BTreeMap::new(),
                deployments: BTreeMap::new(),
                events,
            }),
            persistence,
            persistence_sync,
            invocation_persistence,
        };
        // The database is authoritative for metrics. Filesystem manifests are
        // then used to recover artifacts that predate or missed a DB snapshot.
        console.load_stored_services(stored_services)?;
        console.load_manifest_services()?;
        console.load_stored_deployments(stored_deployments)?;
        console.restore_deployments()?;
        console.persist_snapshot().await?;
        Ok(console)
    }

    /// Returns the configured component upload limit.
    pub fn max_artifact_bytes(&self) -> usize {
        self.max_artifact_bytes
    }

    /// Flushes invocation telemetry accepted before this call.
    pub async fn flush_invocation_telemetry(&self) {
        self.invocation_persistence.flush().await;
    }

    /// Returns the configured binary WIT package upload limit.
    pub fn max_wit_package_bytes(&self) -> usize {
        self.wit_registry.max_package_bytes()
    }

    /// Returns the maximum raw request size accepted by the Gateway.
    pub fn max_gateway_input_bytes(&self) -> usize {
        self.registration_limits.max_input_bytes
    }

    /// Returns registered service versions in stable key order.
    pub fn services(&self) -> Result<Vec<ServiceView>, ConsoleError> {
        let state = self.state()?;
        Ok(state.services.values().map(ServiceRecord::view).collect())
    }

    /// Returns recent events from newest to oldest.
    pub fn events(&self) -> Result<Vec<EventView>, ConsoleError> {
        let state = self.state()?;
        Ok(state.events.iter().cloned().collect())
    }

    /// Returns active deployments in stable service id order.
    pub fn deployments(&self) -> Result<Vec<DeploymentView>, ConsoleError> {
        let state = self.state()?;
        state
            .deployments
            .iter()
            .map(|(service_id, deployment)| deployment_view(&state, service_id, deployment))
            .collect()
    }

    /// Returns the active deployment for one public service id.
    pub fn deployment(&self, service_id: &str) -> Result<DeploymentView, ConsoleError> {
        let state = self.state()?;
        let deployment = state
            .deployments
            .get(service_id)
            .ok_or_else(|| ConsoleError::DeploymentNotFound(service_id.to_owned()))?;
        deployment_view(&state, service_id, deployment)
    }

    fn runtime_view(&self) -> Result<RuntimeView, ConsoleError> {
        let runtime = self.runtime()?;
        let state = self.state()?;
        match runtime.as_ref() {
            Some(runtime) => {
                let stats = runtime.stats()?;
                Ok(RuntimeView {
                    status: RuntimeStatus::Running,
                    uptime_ms: state
                        .runtime_started_at
                        .map(|started_at| duration_ms(started_at.elapsed()))
                        .unwrap_or_default(),
                    managed_services: state.services.len(),
                    registered_services: stats.registered_services,
                    running_services: stats.running_actors,
                })
            }
            None => Ok(RuntimeView {
                status: RuntimeStatus::Stopped,
                uptime_ms: 0,
                managed_services: state.services.len(),
                registered_services: 0,
                running_services: 0,
            }),
        }
    }

    fn start_runtime(&self) -> Result<RuntimeView, ConsoleError> {
        let _lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let mut runtime = self
            .runtime
            .write()
            .map_err(|_| ConsoleError::LockPoisoned)?;

        if runtime.is_some() {
            drop(runtime);
            return self.runtime_view();
        }

        match self.build_runtime() {
            Ok(started) => {
                *runtime = Some(started);
                drop(runtime);
                self.mark_runtime_started()?;
                self.push_event(
                    EventKind::Started,
                    None,
                    "Wasmeld Runtime started".to_owned(),
                )?;
                self.runtime_view()
            }
            Err(error) => {
                drop(runtime);
                self.push_event(
                    EventKind::Failed,
                    None,
                    format!("Wasmeld Runtime failed to start: {error}"),
                )?;
                Err(error)
            }
        }
    }

    fn stop_runtime(&self) -> Result<RuntimeView, ConsoleError> {
        let _lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let mut runtime = self
            .runtime
            .write()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let Some(running) = runtime.take() else {
            drop(runtime);
            return self.runtime_view();
        };
        let stopped = running.stop_all();
        drop(runtime);
        self.mark_runtime_stopped()?;

        match stopped {
            Ok(()) => {
                self.push_event(
                    EventKind::Stopped,
                    None,
                    "Wasmeld Runtime stopped".to_owned(),
                )?;
                self.runtime_view()
            }
            Err(error) => {
                self.push_event(
                    EventKind::Failed,
                    None,
                    format!("Wasmeld Runtime stopped with an actor shutdown error: {error}"),
                )?;
                Err(error.into())
            }
        }
    }

    fn restart_runtime(&self) -> Result<RuntimeView, ConsoleError> {
        let _lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let mut runtime = self
            .runtime
            .write()
            .map_err(|_| ConsoleError::LockPoisoned)?;

        if let Some(running) = runtime.take()
            && let Err(error) = running.stop_all()
        {
            drop(runtime);
            self.mark_runtime_stopped()?;
            self.push_event(
                EventKind::Failed,
                None,
                format!("Wasmeld Runtime failed to restart while stopping actors: {error}"),
            )?;
            return Err(error.into());
        }

        match self.build_runtime() {
            Ok(started) => {
                *runtime = Some(started);
                drop(runtime);
                self.mark_runtime_started()?;
                self.push_event(
                    EventKind::Started,
                    None,
                    "Wasmeld Runtime restarted".to_owned(),
                )?;
                self.runtime_view()
            }
            Err(error) => {
                drop(runtime);
                self.mark_runtime_stopped()?;
                self.push_event(
                    EventKind::Failed,
                    None,
                    format!("Wasmeld Runtime failed to restart: {error}"),
                )?;
                Err(error)
            }
        }
    }

    fn build_runtime(&self) -> Result<Runtime, ConsoleError> {
        let state = self.state()?;
        let manifests = state
            .services
            .values()
            .map(|service| service.manifest.clone())
            .collect::<Vec<_>>();
        let deployments = state
            .deployments
            .iter()
            .map(|(service_id, deployment)| {
                ServiceKey::new(service_id.clone(), deployment.active_revision.clone())
            })
            .collect::<Result<Vec<_>, _>>()?;
        drop(state);

        let runtime = Runtime::new(self.runtime_config.clone())?;
        for manifest in manifests {
            runtime.register_from_file(manifest)?;
        }
        for key in deployments {
            runtime.start(&key, Vec::new())?;
        }
        Ok(runtime)
    }

    fn mark_runtime_started(&self) -> Result<(), ConsoleError> {
        let mut state = self.state()?;
        let deployed = state
            .deployments
            .iter()
            .map(|(service_id, deployment)| {
                ServiceKey::new(service_id.clone(), deployment.active_revision.clone())
            })
            .collect::<Result<Vec<_>, _>>()?;
        let updated_at_ms = unix_time_ms();
        state.runtime_started_at = Some(Instant::now());
        for (key, service) in &mut state.services {
            let status = if deployed.contains(key) {
                ServiceStatus::Running
            } else {
                ServiceStatus::Stopped
            };
            if service.status != status {
                service.status = status;
                service.updated_at_ms = updated_at_ms;
            }
        }
        Ok(())
    }

    fn mark_runtime_stopped(&self) -> Result<(), ConsoleError> {
        let mut state = self.state()?;
        let updated_at_ms = unix_time_ms();
        state.runtime_started_at = None;
        for service in state.services.values_mut() {
            if !matches!(service.status, ServiceStatus::Stopped) {
                service.status = ServiceStatus::Stopped;
                service.updated_at_ms = updated_at_ms;
            }
        }
        Ok(())
    }

    fn register_package(&self, package_bytes: Vec<u8>) -> Result<ServiceView, ConsoleError> {
        if package_bytes.len() > self.max_artifact_bytes {
            return Err(ConsoleError::ArtifactTooLarge {
                limit: self.max_artifact_bytes,
            });
        }
        let ComponentPackage {
            manifest: package_manifest,
            component: component_bytes,
        } = read_package(&package_bytes, self.max_artifact_bytes)?;

        validate_path_segment("id", &package_manifest.id)?;
        validate_path_segment("revision", &package_manifest.revision)?;
        let key = ServiceKey::new(
            package_manifest.id.clone(),
            package_manifest.revision.clone(),
        )?;
        let _lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;

        if self.state()?.services.contains_key(&key) {
            return Err(RuntimeError::ServiceAlreadyRegistered(key).into());
        }

        let basename = format!("{}@{}", package_manifest.id, package_manifest.revision);
        let component_path = self.artifact_dir.join(format!("{basename}.wasm"));
        let manifest_path = self.artifact_dir.join(format!("{basename}.toml"));
        let manifest = ServiceManifest {
            id: package_manifest.id,
            revision: package_manifest.revision,
            component: component_path.display().to_string(),
            world: package_manifest.world,
            limits: self.registration_limits.clone(),
        };
        manifest.validate()?;

        // Artifacts are committed before Runtime registration so
        // `register_from_file` can recover the same bytes after restart.
        // Every later fallible step removes files it created until the Runtime
        // owns the registration. The HTTP wrapper persists the state snapshot
        // after this method returns.
        write_atomic(&component_path, &component_bytes)?;
        let manifest_toml = toml::to_string_pretty(&manifest)?;
        if let Err(error) = write_atomic(&manifest_path, manifest_toml.as_bytes()) {
            let _ = fs::remove_file(&component_path);
            return Err(error);
        }

        if let Err(error) = runtime.register(manifest.clone(), &component_bytes) {
            let _ = fs::remove_file(&component_path);
            let _ = fs::remove_file(&manifest_path);
            return Err(error.into());
        }
        let capabilities = runtime_capabilities(runtime, &key)?;

        let view = self.insert_service(manifest, capabilities, ServiceStatus::Stopped)?;
        self.push_event(
            EventKind::Registered,
            Some(&key),
            "component package registered".to_owned(),
        )?;
        Ok(view)
    }

    fn unregister(&self, key: &ServiceKey) -> Result<ServiceView, ConsoleError> {
        let _lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let view = {
            let state = self.state()?;
            if state
                .deployments
                .get(key.id())
                .is_some_and(|deployment| deployment.active_revision == key.revision())
            {
                return Err(ConsoleError::ActiveDeployment(key.clone()));
            }
            state
                .services
                .get(key)
                .map(ServiceRecord::view)
                .ok_or_else(|| ConsoleError::ServiceNotFound(key.clone()))?
        };

        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        runtime.unregister(key)?;
        self.state()?.services.remove(key);

        for path in [
            self.artifact_dir.join(format!("{key}.wasm")),
            self.artifact_dir.join(format!("{key}.toml")),
        ] {
            if let Err(error) = fs::remove_file(&path)
                && error.kind() != io::ErrorKind::NotFound
            {
                tracing::warn!(path = %path.display(), %error, "failed to remove unregistered artifact");
            }
        }
        self.push_event(
            EventKind::Unregistered,
            Some(key),
            "component revision unregistered".to_owned(),
        )?;
        Ok(view)
    }

    fn start(&self, key: &ServiceKey, init_config: Vec<u8>) -> Result<ServiceView, ConsoleError> {
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        self.ensure_managed(key)?;
        runtime.start(key, init_config)?;
        let view = self.update_status(key, ServiceStatus::Running)?;
        self.push_event(EventKind::Started, Some(key), "actor started".to_owned())?;
        Ok(view)
    }

    fn stop(&self, key: &ServiceKey) -> Result<ServiceView, ConsoleError> {
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        self.ensure_managed(key)?;
        runtime.stop(key)?;
        let view = self.update_status(key, ServiceStatus::Stopped)?;
        self.push_event(EventKind::Stopped, Some(key), "actor stopped".to_owned())?;
        Ok(view)
    }

    fn restart(&self, key: &ServiceKey, init_config: Vec<u8>) -> Result<ServiceView, ConsoleError> {
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        self.ensure_managed(key)?;
        match runtime.stop(key) {
            Ok(()) | Err(RuntimeError::ActorUnavailable(_)) => {}
            Err(error) => return Err(error.into()),
        }
        runtime.start(key, init_config)?;
        let view = self.update_status(key, ServiceStatus::Running)?;
        self.push_event(EventKind::Started, Some(key), "actor restarted".to_owned())?;
        Ok(view)
    }

    fn activate_deployment(
        &self,
        service_id: String,
        revision: String,
        init_config: Vec<u8>,
    ) -> Result<DeploymentView, ConsoleError> {
        validate_path_segment("id", &service_id)?;
        validate_path_segment("revision", &revision)?;
        let key = ServiceKey::new(service_id.clone(), revision.clone())?;
        let _lifecycle = self
            .lifecycle
            .lock()
            .map_err(|_| ConsoleError::LockPoisoned)?;
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        self.ensure_managed(&key)?;

        // Starting is idempotent. The old revision stays resident so calls
        // that resolved immediately before this switch can finish safely and
        // rollback does not require recompilation.
        runtime.start(&key, init_config)?;

        let updated_at_ms = unix_time_ms();
        let view = {
            let mut state = self.state()?;
            let service = state
                .services
                .get_mut(&key)
                .ok_or_else(|| ConsoleError::ServiceNotFound(key.clone()))?;
            service.status = ServiceStatus::Running;
            service.updated_at_ms = updated_at_ms;
            let deployment = DeploymentRecord {
                active_revision: revision,
                updated_at_ms,
            };
            state
                .deployments
                .insert(service_id.clone(), deployment.clone());
            deployment_view(&state, &service_id, &deployment)?
        };
        self.push_event(
            EventKind::Deployed,
            Some(&key),
            "deployment activated".to_owned(),
        )?;
        Ok(view)
    }

    fn invoke(&self, key: &ServiceKey, input: Vec<u8>) -> Result<InvocationResult, ConsoleError> {
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        self.ensure_managed(key)?;
        let started_at = Instant::now();
        let result = runtime.invoke(key, input);
        let latency_ms = duration_ms(started_at.elapsed());

        // Update counters synchronously so management reads immediately
        // observe this call. Durable telemetry is queued below and is
        // intentionally best-effort: queue saturation or a process crash can
        // lose recent counters/events. Do not use these values for billing.
        let mut state = self.state()?;
        let record = state
            .services
            .get_mut(key)
            .ok_or_else(|| ConsoleError::ServiceNotFound(key.clone()))?;
        record.calls = record.calls.saturating_add(1);
        record.last_latency_ms = Some(latency_ms);
        record.updated_at_ms = unix_time_ms();

        let (failed, event_kind, event_message) = match &result {
            Ok(_) => (
                false,
                EventKind::Invoked,
                format!("invoke completed in {latency_ms} ms"),
            ),
            Err(error) => {
                record.errors = record.errors.saturating_add(1);
                if matches!(error, RuntimeError::ActorFault { .. }) {
                    record.status = ServiceStatus::Faulted;
                }
                (true, EventKind::Failed, error.to_string())
            }
        };
        let updated_at_ms = record.updated_at_ms;
        drop(state);
        let event = self.push_event(event_kind, Some(key), event_message)?;
        self.invocation_persistence.record(InvocationUpdate {
            service_key: key.to_string(),
            failed,
            updated_at_ms,
            latency_ms,
            event: event.into(),
        });

        match result {
            Ok(output) => Ok(InvocationResult { output, latency_ms }),
            Err(error) => Err(error.into()),
        }
    }

    fn gateway_invoke(
        &self,
        service_id: &str,
        input: Vec<u8>,
    ) -> Result<GatewayInvocation, ConsoleError> {
        // Clone the selected revision while holding state, then release the
        // lock before entering Runtime. An activation racing this call affects
        // only subsequent resolutions; this call remains pinned to `revision`.
        let revision = {
            let state = self.state()?;
            state
                .deployments
                .get(service_id)
                .map(|deployment| deployment.active_revision.clone())
                .ok_or_else(|| ConsoleError::DeploymentNotFound(service_id.to_owned()))?
        };
        let key = ServiceKey::new(service_id.to_owned(), revision.clone())?;
        let result = self.invoke(&key, input)?;
        Ok(GatewayInvocation { revision, result })
    }

    fn load_stored_services(
        &self,
        stored_services: Vec<StoredService>,
    ) -> Result<(), ConsoleError> {
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        for stored in stored_services {
            let manifest = ServiceManifest::from_toml(&stored.manifest_toml)?;
            validate_path_segment("id", &manifest.id)?;
            validate_path_segment("revision", &manifest.revision)?;
            let key = manifest.key()?;
            if stored.service_key != key.to_string() {
                return Err(ConsoleError::InvalidDatabaseData(format!(
                    "service key {:?} does not match manifest key {key}",
                    stored.service_key
                )));
            }
            runtime.register_from_file(manifest.clone())?;
            let capabilities = runtime_capabilities(runtime, &key)?;
            self.state()?.services.insert(
                key,
                ServiceRecord {
                    manifest,
                    capabilities,
                    status: ServiceStatus::Stopped,
                    updated_at_ms: stored.updated_at_ms,
                    calls: stored.calls,
                    errors: stored.errors,
                    last_latency_ms: stored.last_latency_ms,
                },
            );
        }
        Ok(())
    }

    fn load_manifest_services(&self) -> Result<(), ConsoleError> {
        let runtime = self.runtime()?;
        let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
        let mut manifests = fs::read_dir(&self.artifact_dir)
            .map_err(|source| ConsoleError::Storage {
                path: self.artifact_dir.clone(),
                source,
            })?
            .filter_map(Result::ok)
            .map(|entry| entry.path())
            .filter(|path| {
                path.extension()
                    .is_some_and(|extension| extension == "toml")
            })
            .collect::<Vec<_>>();
        manifests.sort();

        for manifest_path in manifests {
            let source =
                fs::read_to_string(&manifest_path).map_err(|source| ConsoleError::Storage {
                    path: manifest_path.clone(),
                    source,
                })?;
            let manifest = ServiceManifest::from_toml(&source)?;
            validate_path_segment("id", &manifest.id)?;
            validate_path_segment("revision", &manifest.revision)?;
            let key = manifest.key()?;
            if self.state()?.services.contains_key(&key) {
                continue;
            }
            runtime.register_from_file(manifest.clone())?;
            let capabilities = runtime_capabilities(runtime, &key)?;
            self.insert_service(manifest, capabilities, ServiceStatus::Stopped)?;
        }

        Ok(())
    }

    fn load_stored_deployments(
        &self,
        stored_deployments: Vec<StoredDeployment>,
    ) -> Result<(), ConsoleError> {
        let mut state = self.state()?;
        for stored in stored_deployments {
            validate_path_segment("id", &stored.service_id)?;
            validate_path_segment("revision", &stored.active_revision)?;
            let key = ServiceKey::new(stored.service_id.clone(), stored.active_revision.clone())?;
            if !state.services.contains_key(&key) {
                return Err(ConsoleError::InvalidDatabaseData(format!(
                    "deployment for {:?} references unmanaged revision {key}",
                    stored.service_id
                )));
            }
            state.deployments.insert(
                stored.service_id,
                DeploymentRecord {
                    active_revision: stored.active_revision,
                    updated_at_ms: stored.updated_at_ms,
                },
            );
        }
        Ok(())
    }

    fn restore_deployments(&self) -> Result<(), ConsoleError> {
        let deployments = {
            let state = self.state()?;
            state
                .deployments
                .iter()
                .map(|(service_id, deployment)| {
                    ServiceKey::new(service_id.clone(), deployment.active_revision.clone())
                })
                .collect::<Result<Vec<_>, _>>()?
        };
        {
            let runtime = self.runtime()?;
            let runtime = runtime.as_ref().ok_or(ConsoleError::RuntimeNotRunning)?;
            for key in &deployments {
                runtime.start(key, Vec::new())?;
            }
        }

        let mut state = self.state()?;
        for key in deployments {
            let service = state
                .services
                .get_mut(&key)
                .ok_or_else(|| ConsoleError::ServiceNotFound(key.clone()))?;
            service.status = ServiceStatus::Running;
        }
        Ok(())
    }

    async fn persist_snapshot(&self) -> Result<(), ConsoleError> {
        // Serialize snapshots so a slower earlier write cannot overwrite a
        // newer state captured by a concurrent HTTP operation. This mutex is
        // also shared with the invocation delta writer, which prevents the two
        // transaction styles from interleaving.
        let _sync = self.persistence_sync.lock().await;
        let (services, events, deployments) = self.persistence_snapshot()?;
        self.persistence.sync(services, events, deployments).await?;
        Ok(())
    }

    fn persistence_snapshot(&self) -> Result<PersistenceSnapshot, ConsoleError> {
        let state = self.state()?;
        let services = state
            .services
            .iter()
            .map(|(key, service)| {
                Ok(StoredService {
                    service_key: key.to_string(),
                    manifest_toml: toml::to_string_pretty(&service.manifest)?,
                    updated_at_ms: service.updated_at_ms,
                    calls: service.calls,
                    errors: service.errors,
                    last_latency_ms: service.last_latency_ms,
                })
            })
            .collect::<Result<Vec<_>, ConsoleError>>()?;
        let events = state
            .events
            .iter()
            .map(|event| StoredEvent {
                id: event.id,
                timestamp_ms: event.timestamp_ms,
                kind: event.kind.as_database_value().to_owned(),
                service_id: event.service_id.clone(),
                revision: event.revision.clone(),
                message: event.message.clone(),
            })
            .collect();
        let deployments = state
            .deployments
            .iter()
            .map(|(service_id, deployment)| StoredDeployment {
                service_id: service_id.clone(),
                active_revision: deployment.active_revision.clone(),
                updated_at_ms: deployment.updated_at_ms,
            })
            .collect();
        Ok((services, events, deployments))
    }

    fn insert_service(
        &self,
        manifest: ServiceManifest,
        capabilities: Vec<CapabilityView>,
        status: ServiceStatus,
    ) -> Result<ServiceView, ConsoleError> {
        let key = manifest.key()?;
        let mut state = self.state()?;
        let record = ServiceRecord {
            manifest,
            capabilities,
            status,
            updated_at_ms: unix_time_ms(),
            calls: 0,
            errors: 0,
            last_latency_ms: None,
        };
        let view = record.view();
        state.services.insert(key, record);
        Ok(view)
    }

    fn update_status(
        &self,
        key: &ServiceKey,
        status: ServiceStatus,
    ) -> Result<ServiceView, ConsoleError> {
        let mut state = self.state()?;
        let record = state
            .services
            .get_mut(key)
            .ok_or_else(|| ConsoleError::ServiceNotFound(key.clone()))?;
        record.status = status;
        record.updated_at_ms = unix_time_ms();
        Ok(record.view())
    }

    fn ensure_managed(&self, key: &ServiceKey) -> Result<(), ConsoleError> {
        if self.state()?.services.contains_key(key) {
            Ok(())
        } else {
            Err(ConsoleError::ServiceNotFound(key.clone()))
        }
    }

    fn push_event(
        &self,
        kind: EventKind,
        key: Option<&ServiceKey>,
        message: String,
    ) -> Result<EventView, ConsoleError> {
        let mut state = self.state()?;
        let event = EventView {
            id: state.next_event_id,
            timestamp_ms: unix_time_ms(),
            kind,
            service_id: key.map(|key| key.id().to_owned()),
            revision: key.map(|key| key.revision().to_owned()),
            message,
        };
        state.next_event_id = state.next_event_id.saturating_add(1);
        state.events.push_front(event.clone());
        state.events.truncate(MAX_EVENTS);
        Ok(event)
    }

    fn state(&self) -> Result<MutexGuard<'_, ConsoleState>, ConsoleError> {
        self.state.lock().map_err(|_| ConsoleError::LockPoisoned)
    }

    fn runtime(&self) -> Result<RwLockReadGuard<'_, Option<Runtime>>, ConsoleError> {
        self.runtime.read().map_err(|_| ConsoleError::LockPoisoned)
    }
}

fn runtime_capabilities(
    runtime: &Runtime,
    key: &ServiceKey,
) -> Result<Vec<CapabilityView>, ConsoleError> {
    Ok(runtime
        .capabilities(key)?
        .into_iter()
        .map(Into::into)
        .collect())
}

fn deployment_view(
    state: &ConsoleState,
    service_id: &str,
    deployment: &DeploymentRecord,
) -> Result<DeploymentView, ConsoleError> {
    let key = ServiceKey::new(service_id.to_owned(), deployment.active_revision.clone())?;
    let service = state
        .services
        .get(&key)
        .ok_or_else(|| ConsoleError::ServiceNotFound(key))?;
    Ok(DeploymentView {
        service_id: service_id.to_owned(),
        active_revision: deployment.active_revision.clone(),
        status: service.status,
        updated_at_ms: deployment.updated_at_ms,
    })
}

fn api_key(id: String, revision: String) -> Result<ServiceKey, ConsoleError> {
    validate_path_segment("id", &id)?;
    validate_path_segment("revision", &revision)?;
    ServiceKey::new(id, revision).map_err(Into::into)
}

fn validate_path_segment(name: &str, value: &str) -> Result<(), ConsoleError> {
    let valid = !value.is_empty()
        && value.len() <= 128
        && value
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.'));
    if valid {
        Ok(())
    } else {
        Err(ConsoleError::InvalidRequest(format!(
            "{name} must contain only ASCII letters, digits, '.', '_' or '-' and be at most 128 bytes"
        )))
    }
}

fn decode_optional_base64(value: Option<&str>) -> Result<Vec<u8>, ConsoleError> {
    match value {
        Some(value) if !value.is_empty() => BASE64.decode(value).map_err(|error| {
            ConsoleError::InvalidRequest(format!("init_config_base64 is invalid: {error}"))
        }),
        _ => Ok(Vec::new()),
    }
}

fn write_atomic(path: &Path, bytes: &[u8]) -> Result<(), ConsoleError> {
    let file_name = path
        .file_name()
        .and_then(|name| name.to_str())
        .unwrap_or("artifact");
    let temporary = path.with_file_name(format!(".{file_name}.tmp-{}", std::process::id()));
    fs::write(&temporary, bytes).map_err(|source| ConsoleError::Storage {
        path: temporary.clone(),
        source,
    })?;
    if let Err(source) = fs::rename(&temporary, path) {
        let _ = fs::remove_file(&temporary);
        return Err(ConsoleError::Storage {
            path: path.to_path_buf(),
            source,
        });
    }
    Ok(())
}

fn unix_time_ms() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(duration_ms)
        .unwrap_or_default()
}

fn duration_ms(duration: std::time::Duration) -> u64 {
    u64::try_from(duration.as_millis()).unwrap_or(u64::MAX)
}
