use super::*;
use oci_rust_sdk::virtual_network::{Lifetime, ListPublicIpsRequest, Scope};
use sonic_rs::JsonValueTrait;
use std::{str::FromStr, sync::Arc};

pub struct OciListNeighbors {
    worker_port: u16,
    oci_client: Arc<dyn oci_rust_sdk::virtual_network::VirtualNetwork>,
}

impl OciListNeighbors {
    pub fn new(
        worker_port: u16,
        oci_client: Arc<dyn oci_rust_sdk::virtual_network::VirtualNetwork>,
    ) -> Self {
        Self {
            worker_port,
            oci_client,
        }
    }

    async fn list_public_ips(
        &self,
        compartment_id: String,
    ) -> Result<Vec<std::net::IpAddr>, anyhow::Error> {
        let mut ips = vec![];
        let mut next_page = None;

        loop {
            let result = self
                .oci_client
                .list_public_ips(ListPublicIpsRequest {
                    scope: Scope::AvailabilityDomain,
                    compartment_id: compartment_id.clone(),
                    limit: None,
                    page: next_page,
                    availability_domain: None,
                    lifetime: Some(Lifetime::Ephemeral),
                    public_ip_pool_id: None,
                })
                .await?;

            ips.extend(
                result
                    .items
                    .into_iter()
                    .map(|ip| IpAddr::from_str(&ip.ip_address).unwrap()),
            );
            next_page = result.opc_next_page;
            if next_page.is_none() {
                break;
            }
        }

        Ok(ips)
    }
}

impl ListNeighbors for OciListNeighbors {
    async fn list_neighbors(&self) -> Result<Vec<std::net::IpAddr>, anyhow::Error> {
        let compartment_id = get_compartment_id().await?;
        let public_ips = self.list_public_ips(compartment_id).await?;

        let worker_port = self.worker_port;
        let futures = public_ips.into_iter().map(move |ip| async move {
            let is_worker = reqwest::get(format!("http://{ip}:{worker_port}/role"))
                .await?
                .text()
                .await?
                == "worker";
            Ok::<Option<IpAddr>, anyhow::Error>(is_worker.then_some(ip))
        });
        let neighbors = futures::future::try_join_all(futures)
            .await?
            .into_iter()
            .flatten()
            .collect::<Vec<_>>();

        Ok(neighbors)
    }
}

async fn check_role_worker(ip: IpAddr, worker_port: u16) -> Result<bool, anyhow::Error> {
    Ok(reqwest::get(format!("http://{}:{}/role", ip, worker_port))
        .await?
        .text()
        .await?
        == "worker")
}

async fn get_compartment_id() -> Result<String, anyhow::Error> {
    let text = reqwest::get("http://169.254.169.254/opc/v2/instance/")
        .await?
        .text()
        .await?;
    let object: sonic_rs::Object = sonic_rs::from_str(&text)?;
    let compartment_id = object.get(&"compartmentId").unwrap().as_str().unwrap();
    Ok(compartment_id.to_string())
}
