//! Network management operations.

use crate::DockerClient;
use crate::error::{DockerError, Result};
use bollard::models::*;
use bollard::network::*;
use tracing::{debug, info};

/// Network operations manager.
pub struct Networks<'a> {
    client: &'a DockerClient,
}

impl<'a> Networks<'a> {
    pub(crate) fn new(client: &'a DockerClient) -> Self {
        Self { client }
    }

    /// Create a new network.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     let network_id = client.networks()
    ///         .create("my-network", "bridge")
    ///         .await?;
    ///     println!("Created network: {}", network_id);
    ///     Ok(())
    /// }
    /// ```
    pub async fn create(
        &self,
        name: impl Into<String>,
        driver: impl Into<String>,
    ) -> Result<String> {
        let name = name.into();
        info!("Creating network: {}", name);

        let config = CreateNetworkOptions {
            name: name.clone(),
            driver: driver.into(),
            ..Default::default()
        };

        let response = self
            .client
            .docker
            .create_network(config)
            .await
            .map_err(|e| DockerError::Other(format!("Failed to create network: {}", e)))?;

        info!("Network created: {}", response.id);
        Ok(response.id)
    }

    /// List all networks.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     let networks = client.networks().list().await?;
    ///     for network in networks {
    ///         println!("{}: {:?}", network.id.unwrap(), network.name);
    ///     }
    ///     Ok(())
    /// }
    /// ```
    pub async fn list(&self) -> Result<Vec<Network>> {
        self.client
            .docker
            .list_networks(Some(
                bollard::query_parameters::ListNetworksOptions::default(),
            ))
            .await
            .map_err(|e| DockerError::Other(format!("Failed to list networks: {}", e)))
    }

    /// Get a reference to a specific network.
    pub fn get(&self, name_or_id: impl Into<String>) -> NetworkRef<'a> {
        NetworkRef::new(self.client, name_or_id.into())
    }

    /// Prune unused networks.
    pub async fn prune(&self) -> Result<NetworkPruneResponse> {
        info!("Pruning unused networks...");
        self.client
            .docker
            .prune_networks(Some(
                bollard::query_parameters::PruneNetworksOptions::default(),
            ))
            .await
            .map_err(|e| DockerError::Other(format!("Failed to prune networks: {}", e)))
    }
}

/// Reference to a specific network.
pub struct NetworkRef<'a> {
    client: &'a DockerClient,
    id: String,
}

impl<'a> NetworkRef<'a> {
    pub(crate) fn new(client: &'a DockerClient, id: String) -> Self {
        Self { client, id }
    }

    /// Get the network ID.
    pub fn id(&self) -> &str {
        &self.id
    }

    /// Inspect the network to get detailed information.
    pub async fn inspect(&self) -> Result<Network> {
        debug!("Inspecting network: {}", self.id);
        self.client
            .docker
            .inspect_network(
                &self.id,
                Some(bollard::query_parameters::InspectNetworkOptions::default()),
            )
            .await
            .map_err(|e| DockerError::NetworkNotFound(format!("{}: {}", self.id, e)))
    }

    /// Connect a container to this network.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     client.networks()
    ///         .get("my-network")
    ///         .connect("my-container")
    ///         .await?;
    ///     Ok(())
    /// }
    /// ```
    pub async fn connect(&self, container: impl Into<String>) -> Result<()> {
        let container = container.into();
        info!("Connecting container {} to network {}", container, self.id);

        let config = ConnectNetworkOptions {
            container,
            ..Default::default()
        };

        self.client
            .docker
            .connect_network(&self.id, config)
            .await
            .map_err(|e| DockerError::Other(format!("Failed to connect to network: {}", e)))
    }

    /// Disconnect a container from this network.
    pub async fn disconnect(&self, container: impl Into<String>, force: bool) -> Result<()> {
        let container = container.into();
        info!(
            "Disconnecting container {} from network {}",
            container, self.id
        );

        let config = DisconnectNetworkOptions { container, force };

        self.client
            .docker
            .disconnect_network(&self.id, config)
            .await
            .map_err(|e| DockerError::Other(format!("Failed to disconnect from network: {}", e)))
    }

    /// Remove the network.
    pub async fn remove(&self) -> Result<()> {
        info!("Removing network: {}", self.id);
        self.client
            .docker
            .remove_network(&self.id)
            .await
            .map_err(|e| DockerError::Other(format!("Failed to remove network: {}", e)))
    }
}

impl DockerClient {
    /// Access network operations.
    ///
    /// # Example
    ///
    /// ```no_run
    /// use lmrc_docker::DockerClient;
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let client = DockerClient::new()?;
    ///     let networks = client.networks().list().await?;
    ///     Ok(())
    /// }
    /// ```
    pub fn networks(&self) -> Networks<'_> {
        Networks::new(self)
    }
}
