use std::sync::{Arc, Mutex, RwLock};

use log::{info, warn};

use super::{
    interpret::{InterpretError, InterpretStatus, Outcome},
    inventory::Inventory,
    score::Score,
    topology::Topology,
};

type ScoreVec<T> = Vec<Box<dyn Score<T>>>;

pub struct Maestro<T: Topology> {
    inventory: Inventory,
    topology: T,
    scores: Arc<RwLock<ScoreVec<T>>>,
    topology_preparation_result: Mutex<Option<Outcome>>,
}

impl<T: Topology> Maestro<T> {
    pub fn new(inventory: Inventory, topology: T) -> Self {
        Self {
            inventory,
            topology,
            scores: Arc::new(RwLock::new(Vec::new())),
            topology_preparation_result: None.into(),
        }
    }

    /// Ensures the associated Topology is ready for operations.
    /// Delegates the readiness check and potential setup actions to the Topology.
    pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> {
        info!("Ensuring topology '{}' is ready...", self.topology.name());
        let outcome = self.topology.ensure_ready().await?;
        info!(
            "Topology '{}' readiness check complete: {}",
            self.topology.name(),
            outcome.status
        );

        self.topology_preparation_result
            .lock()
            .unwrap()
            .replace(outcome.clone());
        Ok(outcome)
    }

    // Load the inventory and inventory from environment.
    // This function is able to discover the context that it is running in, such as k8s clusters, aws cloud, linux host, etc.
    // When the HARMONY_TOPOLOGY environment variable is not set, it will default to install k3s
    // locally (lazily, if not installed yet, when the first execution occurs) and use that as a topology
    // So, by default, the inventory is a single host that the binary is running on, and the
    // topology is a single node k3s
    //
    // By default :
    // - Linux => k3s
    // - macos, windows => docker compose
    //
    // To run more complex cases like OKDHACluster, either provide the default target in the
    // harmony infrastructure as code or as an environment variable
    pub fn load_from_env() -> Self {
        // Load env var HARMONY_TOPOLOGY
        match std::env::var("HARMONY_TOPOLOGY") {
            Ok(_) => todo!(),
            Err(_) => todo!(),
        }
    }

    pub fn register_all(&mut self, mut scores: ScoreVec<T>) {
        let mut score_mut = self.scores.write().expect("Should acquire lock");
        score_mut.append(&mut scores);
    }

    fn is_topology_initialized(&self) -> bool {
        let result = self.topology_preparation_result.lock().unwrap();
        if let Some(outcome) = result.as_ref() {
            match outcome.status {
                InterpretStatus::SUCCESS => return true,
                _ => return false,
            }
        } else {
            false
        }
    }

    pub async fn interpret(&self, score: Box<dyn Score<T>>) -> Result<Outcome, InterpretError> {
        if !self.is_topology_initialized() {
            warn!(
                "Launching interpret for score {} but Topology {} is not fully initialized!",
                score.name(),
                self.topology.name(),
            );
        }
        info!("Running score {score:?}");
        let interpret = score.create_interpret();
        info!("Launching interpret {interpret:?}");
        let result = interpret.execute(&self.inventory, &self.topology).await;
        info!("Got result {result:?}");
        result
    }

    pub fn scores(&self) -> Arc<RwLock<ScoreVec<T>>> {
        self.scores.clone()
    }
}
