use std::{
    collections::HashMap,
    path::{Path, PathBuf},
    sync::Arc,
};

use anyhow::{Context, Result};
use stratum_reflection_core::{ModeRun, ReflectionMode, RunContext};
use stratum_state::StateTracker;

/// Modes available for Vapora automation, resolved relative to `modes_dir`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VaporaMode {
    CreateAgentTask,
    DeployService,
}

impl VaporaMode {
    fn filename(self) -> &'static str {
        match self {
            Self::CreateAgentTask => "create_agent_task.ncl",
            Self::DeployService => "deploy_vapora_service.ncl",
        }
    }
}

/// Vapora reflection executor.
///
/// Resolves NCL mode files from a local `reflection/modes/` directory and
/// delegates execution to `stratum-reflection-core`.
pub struct VaporaReflection {
    modes_dir: PathBuf,
}

impl VaporaReflection {
    /// Create an executor pointing at the given `modes_dir` (typically
    /// `vapora/reflection/modes/`).
    pub fn new(modes_dir: impl AsRef<Path>) -> Self {
        Self {
            modes_dir: modes_dir.as_ref().to_owned(),
        }
    }

    fn mode_path(&self, mode: VaporaMode) -> PathBuf {
        self.modes_dir.join(mode.filename())
    }

    /// Load and validate a mode NCL file without executing it.
    pub fn load_mode(&self, mode: VaporaMode) -> Result<ReflectionMode> {
        let path = self.mode_path(mode);
        ReflectionMode::load(&path)
            .with_context(|| format!("loading vapora mode {:?} from {}", mode, path.display()))
    }

    /// Execute a Vapora reflection mode.
    ///
    /// `params` is a map of `{key}` placeholder values referenced in step
    /// `cmd` fields. `state` receives `PipelineRun` and `StepRecord` writes.
    pub async fn execute(
        &self,
        mode: VaporaMode,
        params: HashMap<String, String>,
        project: impl Into<String>,
        state: Arc<dyn StateTracker>,
    ) -> Result<ModeRun> {
        let reflection_mode = self.load_mode(mode)?;
        let ctx = RunContext {
            project: project.into(),
            params,
            state,
        };
        reflection_mode
            .execute(&ctx)
            .await
            .with_context(|| format!("executing vapora mode {:?}", mode))
    }
}

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

    #[test]
    fn mode_path_resolves_correctly() {
        let exec = VaporaReflection::new("/tmp/modes");
        assert_eq!(
            exec.mode_path(VaporaMode::CreateAgentTask),
            PathBuf::from("/tmp/modes/create_agent_task.ncl")
        );
        assert_eq!(
            exec.mode_path(VaporaMode::DeployService),
            PathBuf::from("/tmp/modes/deploy_vapora_service.ncl")
        );
    }

    #[test]
    fn modes_dir_resolves_from_manifest() {
        let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        let crates_dir = manifest
            .parent()
            .expect("CARGO_MANIFEST_DIR has a parent (crates/)");
        let project_dir = crates_dir
            .parent()
            .expect("crates/ has a parent (project root)");
        let dir = project_dir.join("reflection").join("modes");
        assert!(dir.ends_with("reflection/modes"));
    }
}
