//! # s3bolt
//!
//! **High-performance S3 file copy tool — concurrent, async, built in Rust.**
//!
//! Copy S3 objects between buckets and prefixes at maximum throughput using
//! server-side copy, adaptive concurrency, and async I/O. Works as a Rust
//! library, a CLI tool, or a Python package (via PyO3).
//!
//! ## Rust usage
//!
//! ```rust,no_run
//! use std::sync::Arc;
//! use s3bolt::config::{CopyConfig, ConcurrencyConfig, FilterConfig};
//! use s3bolt::engine::orchestrator;
//! use s3bolt::progress::reporter::ProgressState;
//! use s3bolt::types::S3Uri;
//!
//! # async fn example() -> s3bolt::error::Result<()> {
//! let config = CopyConfig {
//!     source: S3Uri::parse("s3://src-bucket/prefix/")?,
//!     destination: S3Uri::parse("s3://dst-bucket/prefix/")?,
//!     recursive: true,
//!     sync_mode: false,
//!     dry_run: false,
//!     verify: false,
//!     filters: FilterConfig::default(),
//!     concurrency: ConcurrencyConfig::default(),
//!     checkpoint_path: None,
//!     resume: false,
//!     storage_class: None,
//!     sse: None,
//!     preserve_metadata: false,
//!     source_profile: None,
//!     dest_profile: None,
//! };
//!
//! let progress = Arc::new(ProgressState::default());
//! let manifest = orchestrator::run(config, progress).await?;
//! println!("Copied {} objects", manifest.copied_objects);
//! # Ok(())
//! # }
//! ```
//!
//! ## Architecture
//!
//! - **Async I/O**: Tokio runtime for tens of thousands of concurrent requests
//! - **Server-side copy**: `CopyObject` for ≤ 5 GiB, multipart `UploadPartCopy` for larger
//! - **Adaptive concurrency**: AIMD algorithm backs off on S3 503 throttling
//! - **Bounded pipeline**: listing → filter → semaphore → workers → progress
//!
//! ## Modules
//!
//! - [`config`] — configuration types
//! - [`types`] — core data structures (S3Uri, CopyJob, CopyManifest)
//! - [`error`] — error types
//! - [`filter`] — object filtering (glob, regex, size, date)
//! - [`s3`] — S3 client, listing, and copy operations
//! - [`engine`] — orchestration, concurrency control, checkpointing
//! - [`progress`] — progress reporting

pub mod config;
pub mod engine;
pub mod error;
pub mod filter;
pub mod progress;
pub mod s3;
pub mod types;

// ── Python bindings (only compiled with the "python" feature) ──────────────

#[cfg(feature = "python")]
mod python {
    use std::sync::Arc;

    use crate::config::{ConcurrencyConfig, CopyConfig, FilterConfig};
    use crate::engine::orchestrator;
    use crate::progress::reporter::ProgressState;
    use crate::types::S3Uri;
    use pyo3::exceptions::PyRuntimeError;
    use pyo3::prelude::*;
    use pyo3::types::PyDict;

    /// Package version, injected at compile time from Cargo.toml.
    const VERSION: &str = env!("CARGO_PKG_VERSION");

    /// The Python-facing copy engine. Holds an owned Tokio runtime.
    #[pyclass(name = "S3CopyEngine")]
    pub struct PyS3CopyEngine {
        runtime: tokio::runtime::Runtime,
        source_profile: Option<String>,
        dest_profile: Option<String>,
    }

    #[pymethods]
    impl PyS3CopyEngine {
        #[new]
        #[pyo3(signature = (source_profile=None, dest_profile=None))]
        fn new(source_profile: Option<String>, dest_profile: Option<String>) -> PyResult<Self> {
            let runtime = tokio::runtime::Builder::new_multi_thread()
                .enable_all()
                .thread_name("s3bolt-py-worker")
                .build()
                .map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
            Ok(Self {
                runtime,
                source_profile,
                dest_profile,
            })
        }

        /// Copy objects from source to destination.
        ///
        /// Returns a dict summarizing the copy results.
        #[pyo3(signature = (
            source,
            destination,
            /,
            recursive = false,
            sync_mode = false,
            dry_run = false,
            max_concurrent = 256,
            include = None,
            exclude = None,
            storage_class = None,
        ))]
        #[allow(clippy::too_many_arguments)]
        fn copy(
            &self,
            py: Python<'_>,
            source: &str,
            destination: &str,
            recursive: bool,
            sync_mode: bool,
            dry_run: bool,
            max_concurrent: u32,
            include: Option<Vec<String>>,
            exclude: Option<Vec<String>>,
            storage_class: Option<String>,
        ) -> PyResult<Py<PyAny>> {
            let source_uri =
                S3Uri::parse(source).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
            let dest_uri =
                S3Uri::parse(destination).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;

            let config = CopyConfig {
                source: source_uri,
                destination: dest_uri,
                recursive,
                sync_mode,
                dry_run,
                verify: false,
                filters: FilterConfig {
                    include_globs: include.unwrap_or_default(),
                    exclude_globs: exclude.unwrap_or_default(),
                    ..Default::default()
                },
                concurrency: ConcurrencyConfig {
                    max_concurrent,
                    ..Default::default()
                },
                checkpoint_path: None,
                resume: false,
                storage_class,
                sse: None,
                preserve_metadata: false,
                source_profile: self.source_profile.clone(),
                dest_profile: self.dest_profile.clone(),
            };

            let progress = Arc::new(ProgressState::default());

            // Release the GIL and run the async engine.
            let manifest = py
                .detach(|| self.runtime.block_on(orchestrator::run(config, progress)))
                .map_err(|e: crate::error::S3boltError| PyRuntimeError::new_err(e.to_string()))?;

            // Convert CopyManifest to a Python dict.
            let dict = PyDict::new(py);
            dict.set_item("total_objects", manifest.total_objects)?;
            dict.set_item("total_bytes", manifest.total_bytes)?;
            dict.set_item("copied_objects", manifest.copied_objects)?;
            dict.set_item("copied_bytes", manifest.copied_bytes)?;
            dict.set_item("skipped_objects", manifest.skipped_objects)?;
            dict.set_item("failed_objects", manifest.failed_objects)?;
            dict.set_item("duration_secs", manifest.duration_secs)?;
            dict.set_item("failed_keys", manifest.failed_keys)?;

            Ok(dict.unbind().into())
        }
    }

    /// The native extension module exposed to Python as `s3bolt._s3bolt`.
    #[pymodule]
    #[pyo3(name = "_s3bolt")]
    pub fn init(m: &Bound<'_, PyModule>) -> PyResult<()> {
        m.add("__version__", VERSION)?;
        m.add_class::<PyS3CopyEngine>()?;
        Ok(())
    }
}
