//! Runtime-owned service identity and resource policy.

use std::{fmt, time::Duration};

use serde::{Deserialize, Serialize};

use crate::{error::RuntimeError, resident::ResidentLimits};

/// Immutable identity of one service revision.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ServiceKey {
    id: String,
    revision: String,
}

impl ServiceKey {
    /// Creates a non-empty service identity.
    pub fn new(id: impl Into<String>, revision: impl Into<String>) -> Result<Self, RuntimeError> {
        let id = id.into();
        let revision = revision.into();

        if id.trim().is_empty() || revision.trim().is_empty() {
            return Err(RuntimeError::InvalidServiceKey);
        }

        Ok(Self { id, revision })
    }

    /// Returns the stable service identifier.
    pub fn id(&self) -> &str {
        &self.id
    }

    /// Returns the immutable service revision.
    pub fn revision(&self) -> &str {
        &self.revision
    }
}

impl fmt::Display for ServiceKey {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "{}@{}", self.id, self.revision)
    }
}

/// Runtime manifest generated by the Console for one registered component.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ServiceManifest {
    /// Stable service identifier.
    pub id: String,
    /// Immutable component revision.
    pub revision: String,
    /// Local path to the compiled Component.
    pub component: String,
    /// Fully versioned WIT world implemented by the Component.
    pub world: String,
    /// Platform-owned execution and I/O limits.
    pub limits: ResourceLimits,
}

impl ServiceManifest {
    /// Parses and validates a TOML runtime manifest.
    pub fn from_toml(source: &str) -> Result<Self, RuntimeError> {
        let manifest = toml::from_str::<Self>(source)?;
        manifest.validate()?;
        Ok(manifest)
    }

    /// Derives the map key used by Runtime and Console state.
    pub fn key(&self) -> Result<ServiceKey, RuntimeError> {
        ServiceKey::new(self.id.clone(), self.revision.clone())
    }

    /// Validates component path, WIT world, identity, and resource limits.
    pub fn validate(&self) -> Result<(), RuntimeError> {
        self.key()?;

        if self.component.trim().is_empty() {
            return Err(RuntimeError::InvalidManifest(
                "component must not be empty".to_owned(),
            ));
        }

        wasmeld_package::validate_world(&self.world)
            .map_err(|error| RuntimeError::InvalidManifest(error.to_string()))?;

        self.limits.validate()
    }
}

/// Per-Actor sandbox, scheduling, mailbox, and payload limits.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ResourceLimits {
    /// Maximum linear memory available to the Store.
    pub memory_bytes: usize,
    /// Wasmtime fuel replenished before each component call.
    pub fuel_per_call: u64,
    /// End-to-end deadline for one initialization or invocation.
    pub deadline_ms: u64,
    /// Number of queued commands accepted by the bounded Actor mailbox.
    pub mailbox_capacity: usize,
    /// Maximum request payload size.
    pub max_input_bytes: usize,
    /// Maximum response payload size.
    pub max_output_bytes: usize,
    /// Limits for active events and Host-owned resources.
    #[serde(default)]
    pub resident: ResidentLimits,
}

impl Default for ResourceLimits {
    fn default() -> Self {
        Self {
            memory_bytes: 64 * 1024 * 1024,
            fuel_per_call: 1_000_000,
            deadline_ms: 500,
            mailbox_capacity: 16,
            max_input_bytes: 16 * 1024,
            max_output_bytes: 16 * 1024,
            resident: ResidentLimits::default(),
        }
    }
}

impl ResourceLimits {
    /// Rejects limits that cannot enforce a meaningful execution boundary.
    pub fn validate(&self) -> Result<(), RuntimeError> {
        if self.memory_bytes == 0 {
            return Err(RuntimeError::InvalidManifest(
                "limits.memory_bytes must be greater than zero".to_owned(),
            ));
        }

        if self.fuel_per_call == 0 {
            return Err(RuntimeError::InvalidManifest(
                "limits.fuel_per_call must be greater than zero".to_owned(),
            ));
        }

        if self.deadline_ms == 0 {
            return Err(RuntimeError::InvalidManifest(
                "limits.deadline_ms must be greater than zero".to_owned(),
            ));
        }

        if self.mailbox_capacity == 0 {
            return Err(RuntimeError::InvalidManifest(
                "limits.mailbox_capacity must be greater than zero".to_owned(),
            ));
        }

        if self.max_input_bytes == 0 || self.max_output_bytes == 0 {
            return Err(RuntimeError::InvalidManifest(
                "input and output limits must be greater than zero".to_owned(),
            ));
        }

        self.resident.validate()
    }

    /// Returns the configured call deadline.
    pub fn deadline(&self) -> Duration {
        Duration::from_millis(self.deadline_ms)
    }
}
