use tokio_util::sync::CancellationToken;

/// A synchronous unit of CPU-bound work.
///
/// Implementors receive a request and a [`CancellationToken`] and return a [`Result`]. The
/// token is cancelled when the caller's future is dropped (timeouts, client disconnects) or when
/// an externally-supplied parent token is cancelled (graceful shutdown). Long-running blocking
/// code should poll [`CancellationToken::is_cancelled`] at natural yield points to abort
/// cooperatively.
///
/// Most callers do not implement this trait directly — any
/// `Fn(Request, CancellationToken) -> Result<Response, Error>` closure satisfies it via a
/// blanket impl. Implement it on a named struct when blocking work needs to carry mutable state
/// or be composed with other tower layers under a concrete type.
///
/// # Example — closure
///
/// ```
/// use apollo_blocking::{BlockingService, CancellationToken};
///
/// fn check<S: BlockingService<u32, Response = u32, Error = std::convert::Infallible>>(_: &S) {}
///
/// let svc = |req: u32, _ct: CancellationToken| -> Result<u32, std::convert::Infallible> {
///     Ok(req * 2)
/// };
/// check(&svc);
/// ```
///
/// # Example — named type
///
/// ```
/// use apollo_blocking::{BlockingService, CancellationToken};
///
/// struct Doubler;
///
/// impl BlockingService<u32> for Doubler {
///     type Response = u32;
///     type Error = std::convert::Infallible;
///
///     fn call(&self, req: u32, _ct: CancellationToken) -> Result<Self::Response, Self::Error> {
///         Ok(req * 2)
///     }
/// }
/// ```
pub trait BlockingService<Request> {
    /// The successful return type produced by [`call`](Self::call).
    type Response;

    /// The error type produced by [`call`](Self::call).
    type Error;

    /// Run the blocking work for one request.
    ///
    /// Implementations should periodically poll `ct.is_cancelled()` at natural yield points
    /// inside long-running computations so that cancellation propagates cooperatively.
    fn call(&self, req: Request, ct: CancellationToken) -> Result<Self::Response, Self::Error>;
}

impl<F, Request, Response, Error> BlockingService<Request> for F
where
    F: Fn(Request, CancellationToken) -> Result<Response, Error>,
{
    type Response = Response;
    type Error = Error;

    fn call(&self, req: Request, ct: CancellationToken) -> Result<Self::Response, Self::Error> {
        (self)(req, ct)
    }
}

#[cfg(test)]
mod tests {
    use super::BlockingService;
    use tokio_util::sync::CancellationToken;

    #[test]
    fn closure_blanket_impl_invokes_function() {
        let svc = |req: u32, _ct: CancellationToken| -> Result<u32, ()> { Ok(req + 1) };

        let result = BlockingService::call(&svc, 41, CancellationToken::new());

        assert_eq!(result, Ok(42));
    }

    #[test]
    fn named_type_impl() {
        struct AddOne;

        impl BlockingService<u32> for AddOne {
            type Response = u32;
            type Error = ();

            fn call(
                &self,
                req: u32,
                _ct: CancellationToken,
            ) -> Result<Self::Response, Self::Error> {
                Ok(req + 1)
            }
        }

        let result = AddOne.call(41, CancellationToken::new());

        assert_eq!(result, Ok(42));
    }

    #[test]
    fn closure_can_observe_cancellation() {
        let svc = |_req: (), ct: CancellationToken| -> Result<bool, ()> { Ok(ct.is_cancelled()) };
        let token = CancellationToken::new();
        token.cancel();

        assert_eq!(BlockingService::call(&svc, (), token), Ok(true));
    }
}
