// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

//! Logging layer implementation for Apache OpenDAL.

#![cfg_attr(docsrs, feature(doc_cfg))]
#![deny(missing_docs)]

use std::fmt::Debug;
use std::fmt::Display;
use std::sync::Arc;

use log::Level;
use log::log;
use opendal_core::raw::*;
use opendal_core::*;

static LOGGING_TARGET: &str = "opendal::services";

/// Add [log](https://docs.rs/log/) for every operation.
///
/// # Logging
///
/// - OpenDAL will log in a structured way.
/// - Every operation will start with a `started` log entry.
/// - Every operation will finish with the following status:
///   - `succeeded`: the operation is successful, but might have more to take.
///   - `finished`: the whole operation is finished.
///   - `failed`: the operation returns an error.
/// - The default log level for expected errors is `Warn`.
/// - The default log level for unexpected errors is `Error`.
///
/// # Examples
///
/// ```no_run
/// # use opendal_core::services;
/// # use opendal_core::Operator;
/// # use opendal_core::Result;
/// # use opendal_layer_logging::LoggingLayer;
/// #
/// # fn main() -> Result<()> {
/// let _ = Operator::new(services::Memory::default())?
///     .layer(LoggingLayer::default());
/// # Ok(())
/// # }
/// ```
///
/// # Output
///
/// OpenDAL is using [`log`](https://docs.rs/log/latest/log/) for logging internally.
///
/// To enable logging output, please set `RUST_LOG`:
///
/// ```shell
/// RUST_LOG=debug ./app
/// ```
///
/// To config logging output, please refer to [Configure Logging](https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html):
///
/// ```shell
/// RUST_LOG="info,opendal::services=debug" ./app
/// ```
///
/// # Logging Interceptor
///
/// You can implement your own logging interceptor to customize the logging behavior.
///
/// ```no_run
/// # use opendal_core::raw;
/// # use opendal_core::services;
/// # use opendal_core::Error;
/// # use opendal_core::Operator;
/// # use opendal_core::Result;
/// # use opendal_layer_logging::LoggingInterceptor;
/// # use opendal_layer_logging::LoggingLayer;
/// #
/// #[derive(Debug, Clone)]
/// struct MyLoggingInterceptor;
///
/// impl LoggingInterceptor for MyLoggingInterceptor {
///     fn log(
///         &self,
///         info: &raw::ServiceInfo,
///         operation: raw::Operation,
///         context: &[(&str, &str)],
///         message: &str,
///         err: Option<&Error>,
///     ) {
///         // log something
///     }
/// }
///
/// # fn main() -> Result<()> {
/// let _ = Operator::new(services::Memory::default())?
///     .layer(LoggingLayer::new(MyLoggingInterceptor));
/// # Ok(())
/// # }
/// ```
#[derive(Clone, Copy, Debug)]
pub struct LoggingLayer<I = DefaultLoggingInterceptor> {
    logger: I,
}

impl Default for LoggingLayer {
    fn default() -> Self {
        Self {
            logger: DefaultLoggingInterceptor,
        }
    }
}

impl LoggingLayer {
    /// Create the layer with specific logging interceptor.
    pub fn new<I: LoggingInterceptor>(logger: I) -> LoggingLayer<I> {
        LoggingLayer { logger }
    }
}

impl<I: LoggingInterceptor> Layer for LoggingLayer<I> {
    fn apply_service(&self, inner: Servicer) -> Servicer {
        Arc::new(self.layer(inner))
    }
}

impl<I: LoggingInterceptor> LoggingLayer<I> {
    fn layer(&self, inner: Servicer) -> LoggingService<I> {
        let info = inner.info();
        LoggingService {
            inner,
            info,
            logger: self.logger.clone(),
        }
    }
}

/// LoggingInterceptor customizes log emission.
pub trait LoggingInterceptor: Debug + Clone + Send + Sync + Unpin + 'static {
    /// Called for every log event.
    ///
    /// # Inputs
    ///
    /// - `info`: The service information used for this operation.
    /// - `operation`: The operation being logged.
    /// - `context`: Additional key-value context such as path, range, or counters.
    /// - `message`: The event message, such as `started`, `finished`, or `failed`.
    /// - `err`: The error associated with this event, if any.
    ///
    /// # Performance
    ///
    /// This method runs inline with the operation path. Avoid expensive I/O,
    /// network calls, or long-running work here.
    fn log(
        &self,
        info: &ServiceInfo,
        operation: Operation,
        context: &[(&str, &str)],
        message: &str,
        err: Option<&Error>,
    );
}

/// The DefaultLoggingInterceptor will log the message by the standard logging macro.
#[derive(Clone, Copy, Debug, Default)]
pub struct DefaultLoggingInterceptor;

impl LoggingInterceptor for DefaultLoggingInterceptor {
    fn log(
        &self,
        info: &ServiceInfo,
        operation: Operation,
        context: &[(&str, &str)],
        message: &str,
        err: Option<&Error>,
    ) {
        if let Some(err) = err {
            // Expected errors are logged as warnings; unexpected errors need stronger
            // visibility and more diagnostic context.
            let lvl = if err.kind() == ErrorKind::Unexpected {
                Level::Error
            } else {
                Level::Warn
            };

            log!(
                target: LOGGING_TARGET,
                lvl,
                "service={} name={}{}: {operation} {message} {}",
                info.scheme(),
                info.name(),
                LoggingContext(context),
                // Use Debug for unexpected errors to preserve more context.
                // String avoids conditional format_args! temporaries in this log! argument.
                if err.kind() != ErrorKind::Unexpected {
                    format!("{err}")
                } else {
                    format!("{err:?}")
                }
            );
        }

        log!(
            target: LOGGING_TARGET,
            Level::Debug,
            "service={} name={}{}: {operation} {message}",
            info.scheme(),
            info.name(),
            LoggingContext(context),
        );
    }
}

struct LoggingContext<'a>(&'a [(&'a str, &'a str)]);

impl Display for LoggingContext<'_> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (k, v) in self.0.iter() {
            write!(f, " {k}={v}")?;
        }
        Ok(())
    }
}

#[doc(hidden)]
pub struct LoggingService<I: LoggingInterceptor> {
    inner: Servicer,
    info: ServiceInfo,
    logger: I,
}

impl<I: LoggingInterceptor> Debug for LoggingService<I> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LoggingService")
            .field("inner", &self.inner)
            .field("info", &self.info)
            .finish_non_exhaustive()
    }
}

impl<I: LoggingInterceptor> LoggingService<I> {
    fn log_start(&self, op: Operation, context: &[(&str, &str)]) {
        self.logger.log(&self.info, op, context, "started", None);
    }

    fn log_finish(&self, op: Operation, context: &[(&str, &str)], err: Option<&Error>) {
        let message = if err.is_some() { "failed" } else { "finished" };
        self.logger.log(&self.info, op, context, message, err);
    }
}

impl<I: LoggingInterceptor> Service for LoggingService<I> {
    type Reader = LoggingReader<oio::Reader, I>;
    type Writer = LoggingWriter<oio::Writer, I>;
    type Lister = LoggingLister<oio::Lister, I>;
    type Deleter = LoggingDeleter<oio::Deleter, I>;
    type Copier = LoggingCopier<oio::Copier, I>;

    fn info(&self) -> ServiceInfo {
        self.info.clone()
    }

    fn capability(&self) -> Capability {
        self.inner.capability()
    }

    async fn create_dir(
        &self,
        ctx: &OperationContext,
        path: &str,
        args: OpCreateDir,
    ) -> Result<RpCreateDir> {
        self.log_start(Operation::CreateDir, &[("path", path)]);
        let result = self.inner.create_dir(ctx, path, args).await;
        self.log_finish(
            Operation::CreateDir,
            &[("path", path)],
            result.as_ref().err(),
        );
        result
    }

    fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
        self.log_start(Operation::Read, &[("path", path)]);
        self.inner
            .read(ctx, path, args)
            .map(|r| {
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[("path", path)],
                    "created reader",
                    None,
                );
                LoggingReader::new(self.info.clone(), self.logger.clone(), path, r)
            })
            .inspect_err(|err| {
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[("path", path)],
                    "failed",
                    Some(err),
                );
            })
    }

    fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
        self.log_start(Operation::Write, &[("path", path)]);
        self.inner
            .write(ctx, path, args)
            .map(|w| {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[("path", path)],
                    "created writer",
                    None,
                );
                LoggingWriter::new(self.info.clone(), self.logger.clone(), path, w)
            })
            .inspect_err(|err| {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[("path", path)],
                    "failed",
                    Some(err),
                );
            })
    }

    async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
        self.log_start(Operation::Stat, &[("path", path)]);
        let result = self.inner.stat(ctx, path, args).await;
        self.log_finish(Operation::Stat, &[("path", path)], result.as_ref().err());
        result
    }

    fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
        self.log_start(Operation::Delete, &[]);
        self.inner
            .delete(ctx)
            .map(|d| {
                self.logger
                    .log(&self.info, Operation::Delete, &[], "finished", None);
                LoggingDeleter::new(self.info.clone(), self.logger.clone(), d)
            })
            .inspect_err(|err| {
                self.logger
                    .log(&self.info, Operation::Delete, &[], "failed", Some(err));
            })
    }

    fn copy(
        &self,
        ctx: &OperationContext,
        from: &str,
        to: &str,
        args: OpCopy,
        opts: OpCopier,
    ) -> Result<Self::Copier> {
        self.log_start(Operation::Copy, &[("from", from), ("to", to)]);
        self.inner
            .copy(ctx, from, to, args, opts)
            .map(|c| {
                self.logger.log(
                    &self.info,
                    Operation::Copy,
                    &[("from", from), ("to", to)],
                    "created copier",
                    None,
                );
                LoggingCopier::new(self.info.clone(), self.logger.clone(), from, to, c)
            })
            .inspect_err(|err| {
                self.logger.log(
                    &self.info,
                    Operation::Copy,
                    &[("from", from), ("to", to)],
                    "failed",
                    Some(err),
                );
            })
    }

    async fn rename(
        &self,
        ctx: &OperationContext,
        from: &str,
        to: &str,
        args: OpRename,
    ) -> Result<RpRename> {
        self.log_start(Operation::Rename, &[("from", from), ("to", to)]);
        let result = self.inner.rename(ctx, from, to, args).await;
        self.log_finish(
            Operation::Rename,
            &[("from", from), ("to", to)],
            result.as_ref().err(),
        );
        result
    }

    fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
        self.log_start(Operation::List, &[("path", path)]);
        self.inner
            .list(ctx, path, args)
            .map(|v| {
                self.logger.log(
                    &self.info,
                    Operation::List,
                    &[("path", path)],
                    "created lister",
                    None,
                );
                LoggingLister::new(self.info.clone(), self.logger.clone(), path, v)
            })
            .inspect_err(|err| {
                self.logger.log(
                    &self.info,
                    Operation::List,
                    &[("path", path)],
                    "failed",
                    Some(err),
                );
            })
    }

    async fn presign(
        &self,
        ctx: &OperationContext,
        path: &str,
        args: OpPresign,
    ) -> Result<RpPresign> {
        self.log_start(Operation::Presign, &[("path", path)]);
        let result = self.inner.presign(ctx, path, args).await;
        self.log_finish(Operation::Presign, &[("path", path)], result.as_ref().err());
        result
    }
}

#[doc(hidden)]
pub struct LoggingReader<R, I: LoggingInterceptor> {
    info: ServiceInfo,
    logger: I,
    path: String,
    range: Option<BytesRange>,

    read: u64,
    inner: R,
}

impl<R, I: LoggingInterceptor> LoggingReader<R, I> {
    fn new(info: ServiceInfo, logger: I, path: &str, reader: R) -> Self {
        Self::with_range(info, logger, path, None, reader)
    }

    fn with_range(
        info: ServiceInfo,
        logger: I,
        path: &str,
        range: Option<BytesRange>,
        reader: R,
    ) -> Self {
        Self {
            info,
            logger,
            path: path.to_string(),
            range,

            read: 0,
            inner: reader,
        }
    }

    fn range_label(&self) -> String {
        self.range
            .map(|range| range.to_string())
            .unwrap_or_default()
    }
}

impl<R: oio::ReadStream, I: LoggingInterceptor> oio::ReadStream for LoggingReader<R, I> {
    async fn read(&mut self) -> Result<Buffer> {
        match self.inner.read().await {
            Ok(bs) if bs.is_empty() => {
                let range = self.range_label();
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[
                        ("path", &self.path),
                        ("range", &range),
                        ("read", &self.read.to_string()),
                        ("size", &bs.len().to_string()),
                    ],
                    "finished",
                    None,
                );
                Ok(bs)
            }
            Ok(bs) => {
                self.read += bs.len() as u64;
                Ok(bs)
            }
            Err(err) => {
                let range = self.range_label();
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[
                        ("path", &self.path),
                        ("range", &range),
                        ("read", &self.read.to_string()),
                    ],
                    "failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }
}

impl<R: oio::Read, I: LoggingInterceptor> oio::Read for LoggingReader<R, I> {
    async fn open(&self, range: BytesRange) -> Result<(RpRead, Box<dyn oio::ReadStreamDyn>)> {
        match self.inner.open(range).await {
            Ok((rp, stream)) => Ok((
                rp,
                Box::new(LoggingReader::with_range(
                    self.info.clone(),
                    self.logger.clone(),
                    &self.path,
                    Some(range),
                    stream,
                )) as Box<dyn oio::ReadStreamDyn>,
            )),
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[("path", &self.path), ("range", &range.to_string())],
                    "failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }

    async fn read(&self, range: BytesRange) -> Result<(RpRead, Buffer)> {
        match self.inner.read(range).await {
            Ok((rp, buffer)) => {
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[
                        ("path", &self.path),
                        ("range", &range.to_string()),
                        ("size", &buffer.len().to_string()),
                    ],
                    "finished",
                    None,
                );
                Ok((rp, buffer))
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Read,
                    &[("path", &self.path), ("range", &range.to_string())],
                    "failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }
}

#[doc(hidden)]
pub struct LoggingWriter<W, I> {
    info: ServiceInfo,
    logger: I,
    path: String,

    written: u64,
    inner: W,
}

impl<W, I> LoggingWriter<W, I> {
    fn new(info: ServiceInfo, logger: I, path: &str, writer: W) -> Self {
        Self {
            info,
            logger,
            path: path.to_string(),

            written: 0,
            inner: writer,
        }
    }
}

impl<W: oio::Write, I: LoggingInterceptor> oio::Write for LoggingWriter<W, I> {
    async fn write(&mut self, bs: Buffer) -> Result<()> {
        let size = bs.len();

        match self.inner.write(bs).await {
            Ok(_) => {
                self.written += size as u64;
                Ok(())
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[
                        ("path", &self.path),
                        ("written", &self.written.to_string()),
                        ("size", &size.to_string()),
                    ],
                    "failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }

    async fn abort(&mut self) -> Result<()> {
        match self.inner.abort().await {
            Ok(_) => {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[("path", &self.path), ("written", &self.written.to_string())],
                    "abort succeeded",
                    None,
                );
                Ok(())
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[("path", &self.path), ("written", &self.written.to_string())],
                    "abort failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }

    async fn close(&mut self) -> Result<Metadata> {
        match self.inner.close().await {
            Ok(meta) => {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[("path", &self.path), ("written", &self.written.to_string())],
                    "close succeeded",
                    None,
                );
                Ok(meta)
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Write,
                    &[("path", &self.path), ("written", &self.written.to_string())],
                    "close failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }
}

#[doc(hidden)]
pub struct LoggingLister<P, I: LoggingInterceptor> {
    info: ServiceInfo,
    logger: I,
    path: String,

    listed: usize,
    inner: P,
}

impl<P, I: LoggingInterceptor> LoggingLister<P, I> {
    fn new(info: ServiceInfo, logger: I, path: &str, inner: P) -> Self {
        Self {
            info,
            logger,
            path: path.to_string(),

            listed: 0,
            inner,
        }
    }
}

impl<P: oio::List, I: LoggingInterceptor> oio::List for LoggingLister<P, I> {
    async fn next(&mut self) -> Result<Option<oio::Entry>> {
        let res = self.inner.next().await;

        match &res {
            Ok(Some(_)) => {
                self.listed += 1;
            }
            Ok(None) => {
                self.logger.log(
                    &self.info,
                    Operation::List,
                    &[("path", &self.path), ("listed", &self.listed.to_string())],
                    "finished",
                    None,
                );
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::List,
                    &[("path", &self.path), ("listed", &self.listed.to_string())],
                    "failed",
                    Some(err),
                );
            }
        };

        res
    }
}

#[doc(hidden)]
pub struct LoggingDeleter<D, I: LoggingInterceptor> {
    info: ServiceInfo,
    logger: I,

    deleted: usize,
    inner: D,
}

impl<D, I: LoggingInterceptor> LoggingDeleter<D, I> {
    fn new(info: ServiceInfo, logger: I, inner: D) -> Self {
        Self {
            info,
            logger,

            deleted: 0,
            inner,
        }
    }
}

impl<D: oio::Delete, I: LoggingInterceptor> oio::Delete for LoggingDeleter<D, I> {
    async fn delete(&mut self, path: &str, args: OpDelete) -> Result<()> {
        let version = args
            .version()
            .map(|v| v.to_string())
            .unwrap_or_else(|| "<latest>".to_string());

        let res = self.inner.delete(path, args).await;

        match &res {
            Ok(_) => {
                self.deleted += 1;
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Delete,
                    &[
                        ("path", path),
                        ("version", &version),
                        ("deleted", &self.deleted.to_string()),
                    ],
                    "failed",
                    Some(err),
                );
            }
        };

        res
    }

    async fn close(&mut self) -> Result<()> {
        let res = self.inner.close().await;

        match &res {
            Ok(_) => {
                self.logger.log(
                    &self.info,
                    Operation::Delete,
                    &[("deleted", &self.deleted.to_string())],
                    "succeeded",
                    None,
                );
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Delete,
                    &[("deleted", &self.deleted.to_string())],
                    "failed",
                    Some(err),
                );
            }
        };

        res
    }
}

#[doc(hidden)]
pub struct LoggingCopier<C, I: LoggingInterceptor> {
    info: ServiceInfo,
    logger: I,
    from: String,
    to: String,

    copied: u64,
    inner: C,
}

impl<C, I: LoggingInterceptor> LoggingCopier<C, I> {
    fn new(info: ServiceInfo, logger: I, from: &str, to: &str, inner: C) -> Self {
        Self {
            info,
            logger,
            from: from.to_string(),
            to: to.to_string(),

            copied: 0,
            inner,
        }
    }
}

impl<C: oio::Copy, I: LoggingInterceptor> oio::Copy for LoggingCopier<C, I> {
    async fn next(&mut self) -> Result<Option<usize>> {
        match self.inner.next().await {
            Ok(Some(n)) => {
                self.copied += n as u64;
                Ok(Some(n))
            }
            Ok(None) => {
                self.logger.log(
                    &self.info,
                    Operation::Copy,
                    &[
                        ("from", &self.from),
                        ("to", &self.to),
                        ("copied", &self.copied.to_string()),
                    ],
                    "finished",
                    None,
                );
                Ok(None)
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Copy,
                    &[
                        ("from", &self.from),
                        ("to", &self.to),
                        ("copied", &self.copied.to_string()),
                    ],
                    "failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }

    async fn close(&mut self) -> Result<Metadata> {
        self.inner.close().await
    }

    async fn abort(&mut self) -> Result<()> {
        match self.inner.abort().await {
            Ok(_) => {
                self.logger.log(
                    &self.info,
                    Operation::Copy,
                    &[
                        ("from", &self.from),
                        ("to", &self.to),
                        ("copied", &self.copied.to_string()),
                    ],
                    "abort succeeded",
                    None,
                );
                Ok(())
            }
            Err(err) => {
                self.logger.log(
                    &self.info,
                    Operation::Copy,
                    &[
                        ("from", &self.from),
                        ("to", &self.to),
                        ("copied", &self.copied.to_string()),
                    ],
                    "abort failed",
                    Some(&err),
                );
                Err(err)
            }
        }
    }
}
