Documentation
// Copyright (c) 2025, Salesforce, Inc.,
// All rights reserved.
// For full license text, see the LICENSE.txt file

use std::{fs::File, io::BufRead as _, path::Path};

use bollard::models::ContainerSummary;
use bollard::Docker;
use tokio::sync::OnceCell;

use crate::error::TestError;

static CURRENT: OnceCell<Option<HostContainer>> = OnceCell::const_new();

/// Struct to manage the host container.
#[derive(Debug)]
pub struct HostContainer {
    id: String,
}

impl HostContainer {
    /// Get the id of the host container.
    pub fn id(&self) -> &str {
        &self.id
    }

    async fn init_current(docker: &Docker) -> Result<Option<Self>, TestError> {
        log::info!("Checking running mode.");

        let path = Path::new("/etc/hostname");

        if !path.exists() || !path.is_file() {
            return Ok(None);
        }
        let file = File::open(path)?;

        let Some(hostname) = std::io::BufReader::new(file).lines().next() else {
            return Ok(None);
        };

        let hostname = hostname?;

        let running_containers = docker
            .list_containers(Some(bollard::container::ListContainersOptions::<String> {
                all: false,
                ..Default::default()
            }))
            .await?;
        let hostname = Self::find_matching_container_name(&running_containers, &hostname).await?;

        let inspection = match docker.inspect_container(&hostname, None).await {
            Ok(response) => response,
            Err(bollard::errors::Error::DockerResponseServerError {
                status_code: 404, ..
            }) => return Ok(None),
            Err(e) => return Err(e.into()),
        };

        Ok(Some(Self {
            id: inspection.id.unwrap_or_default(),
        }))
    }

    async fn find_matching_container_name(
        running_containers: &Vec<ContainerSummary>,
        hostname: &str,
    ) -> Result<String, TestError> {
        let mut container_name = hostname.to_string();

        for container in running_containers {
            if let Some(names) = &container.names {
                if let Some(name) = names.first() {
                    log::debug!("Available container: {name}");
                    if name.contains(hostname) {
                        container_name = name.trim_start_matches('/').to_string();
                        log::info!("Found matching container for DinD: {container_name}");
                        return Ok(container_name);
                    }
                }
            }
        }
        log::debug!("No matching container found for DinD. Defaulting to: {container_name}");
        Ok(container_name)
    }

    /// Get the current host container.
    pub(crate) async fn current(docker: &Docker) -> Result<Option<Self>, TestError> {
        let current = CURRENT
            .get_or_try_init(|| Self::init_current(docker))
            .await?
            .as_ref()
            .map(|c| c.private_clone());
        Ok(current)
    }

    /// Private clone of the host container.
    pub(crate) fn private_clone(&self) -> Self {
        Self {
            id: self.id.clone(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_find_matching_container_name() {
        let running_containers = vec![
            ContainerSummary {
                names: Some(vec!["/a-container-8976549123".to_string()]),
                ..Default::default()
            },
            ContainerSummary {
                names: Some(vec!["/another-container-201349124".to_string()]),
                ..Default::default()
            },
            ContainerSummary {
                names: Some(vec!["/another-container-201349125".to_string()]),
                ..Default::default()
            },
        ];

        let result =
            HostContainer::find_matching_container_name(&running_containers, "another-container")
                .await
                .unwrap();
        assert_eq!(result, "another-container-201349124");
    }

    #[tokio::test]
    async fn test_no_matching_container_name() {
        let running_containers = vec![
            ContainerSummary {
                names: Some(vec!["/a-container-8976549123".to_string()]),
                ..Default::default()
            },
            ContainerSummary {
                names: Some(vec!["/another-container-201349124".to_string()]),
                ..Default::default()
            },
            ContainerSummary {
                names: Some(vec!["/another-container-201349125".to_string()]),
                ..Default::default()
            },
        ];

        let result =
            HostContainer::find_matching_container_name(&running_containers, "container-not-found")
                .await
                .unwrap();
        assert_eq!(result, "container-not-found");
    }
}