use std::{future::Future, net::SocketAddr, pin::Pin};

use crate::core::{CoreResult, ShutdownToken};

/// Common service identity used by REST and RPC runtimes.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServiceInfo {
    /// Logical service name.
    pub name: String,
    /// Bound address.
    pub addr: SocketAddr,
}

impl ServiceInfo {
    /// Creates a service identity.
    pub fn new(name: impl Into<String>, addr: SocketAddr) -> Self {
        Self {
            name: name.into(),
            addr,
        }
    }
}

/// Boxed future returned by [`Service`] lifecycle methods.
pub type ServiceFuture<'a> = Pin<Box<dyn Future<Output = CoreResult<()>> + Send + 'a>>;

/// Async service lifecycle managed by [`crate::core::ServiceGroup`].
///
/// Implementors should return from [`Service::start`] when the supplied
/// [`ShutdownToken`] is cancelled. [`Service::stop`] is a best-effort hook used
/// to unblock resources that do not observe the token directly.
pub trait Service: Send + Sync + 'static {
    /// Logical service name used in logs and error messages.
    fn name(&self) -> &str;

    /// Starts the service and runs until shutdown or failure.
    fn start(&self, shutdown: ShutdownToken) -> ServiceFuture<'_>;

    /// Stops the service. The default implementation is a no-op.
    fn stop(&self) -> ServiceFuture<'_> {
        Box::pin(async { Ok(()) })
    }
}

/// Async service backed by a start function.
pub struct FnService<F> {
    name: String,
    start: F,
}

impl<F> FnService<F> {
    /// Creates a function-backed service.
    pub fn new(name: impl Into<String>, start: F) -> Self {
        Self {
            name: name.into(),
            start,
        }
    }
}

impl<F, Fut> Service for FnService<F>
where
    F: Fn(ShutdownToken) -> Fut + Send + Sync + 'static,
    Fut: Future<Output = CoreResult<()>> + Send + 'static,
{
    fn name(&self) -> &str {
        &self.name
    }

    fn start(&self, shutdown: ShutdownToken) -> ServiceFuture<'_> {
        Box::pin((self.start)(shutdown))
    }
}
