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

use log::{debug, warn};

use crate::topology::TopologyStatus;

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

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

pub struct Maestro<T: Topology> {
    inventory: Inventory,
    topology: T,
    scores: Arc<RwLock<ScoreVec<T>>>,
    topology_state: TopologyState,
}

impl<T: Topology> Maestro<T> {
    /// Creates a bare maestro without initialization.
    ///
    /// This should rarely be used. Most of the time Maestro::initialize should be used instead.
    pub fn new_without_initialization(inventory: Inventory, topology: T) -> Self {
        let topology_name = topology.name().to_string();

        Self {
            inventory,
            topology,
            scores: Arc::new(RwLock::new(Vec::new())),
            topology_state: TopologyState::new(topology_name),
        }
    }

    pub async fn initialize(inventory: Inventory, topology: T) -> Result<Self, PreparationError> {
        let mut instance = Self::new_without_initialization(inventory, topology);
        instance.prepare_topology().await?;
        Ok(instance)
    }

    /// Ensures the associated Topology is ready for operations.
    /// Delegates the readiness check and potential setup actions to the Topology.
    async fn prepare_topology(&mut self) -> Result<PreparationOutcome, PreparationError> {
        self.topology_state.prepare();

        let result = self.topology.ensure_ready().await;

        match result {
            Ok(outcome) => {
                match outcome.clone() {
                    PreparationOutcome::Success { details } => {
                        self.topology_state.success(details);
                    }
                    PreparationOutcome::Noop => {
                        self.topology_state.noop();
                    }
                };
                Ok(outcome)
            }
            Err(err) => {
                self.topology_state.error(err.to_string());
                Err(err)
            }
        }
    }

    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 {
        self.topology_state.status == TopologyStatus::Success
    }

    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(),
            );
        }
        debug!("Interpreting score {score:?}");
        let result = score.interpret(&self.inventory, &self.topology).await;
        debug!("Got result {result:?}");
        result
    }

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