use std::sync::Arc;

use async_trait::async_trait;
use serde::Serialize;
use tokio::sync::RwLock;

use crate::{
    data::{Id, Version},
    interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
    inventory::Inventory,
    score::Score,
    topology::Topology,
};

#[derive(Debug, Clone)]
pub struct OPNsenseShellCommandScore {
    // TODO I am pretty sure we should not hold a direct reference to the
    // opnsense_config::Config here.
    // This causes a problem with serialization but also could cause many more problems as this
    // is mixing concerns of configuration (which is the Responsibility of Scores to define)
    // and state/execution which is the responsibility of interprets via topologies to manage
    //
    // I feel like a better solution would be for this Score/Interpret to require
    // Topology + OPNSenseShell trait bindings
    pub opnsense: Arc<RwLock<opnsense_config::Config>>,
    pub command: String,
}

impl Serialize for OPNsenseShellCommandScore {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        todo!("See comment about moving opnsense_config::Config outside the score")
    }
}

impl<T: Topology> Score<T> for OPNsenseShellCommandScore {
    fn create_interpret(&self) -> Box<dyn Interpret<T>> {
        Box::new(OPNsenseShellInterpret {
            status: InterpretStatus::QUEUED,
            score: self.clone(),
        })
    }

    fn name(&self) -> String {
        "OPNSenseShellCommandScore".to_string()
    }
}

#[derive(Debug)]
pub struct OPNsenseShellInterpret {
    pub status: InterpretStatus,
    pub score: OPNsenseShellCommandScore,
}

#[async_trait]
impl<T: Topology> Interpret<T> for OPNsenseShellInterpret {
    async fn execute(
        &self,
        _inventory: &Inventory,
        _topology: &T,
    ) -> Result<Outcome, InterpretError> {
        let output = self
            .score
            .opnsense
            .read()
            .await
            .run_command(&self.score.command)
            .await?;

        Ok(Outcome::success(format!(
            "Command execution successful : {}\n\n{output}",
            self.score.command
        )))
    }

    fn get_name(&self) -> InterpretName {
        InterpretName::OPNSense
    }

    fn get_version(&self) -> Version {
        Version::from("1.0.0").unwrap()
    }

    fn get_status(&self) -> InterpretStatus {
        self.status.clone()
    }

    fn get_children(&self) -> Vec<Id> {
        todo!()
    }
}

impl From<opnsense_config::Error> for InterpretError {
    fn from(value: opnsense_config::Error) -> Self {
        Self::new(format!("opnsense_config::Error {value:?}"))
    }
}
