use anyhow::{Context, Result};
use std::collections::HashMap;
use std::path::PathBuf;
use std::str::FromStr;

use crate::plugin::PluginRegistry;
use crate::pom::Pom;

/// The ordered Maven build lifecycle phases.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Phase {
    Clean,
    Validate,
    Compile,
    Test,
    Package,
    Verify,
    Install,
    Deploy,
}

impl FromStr for Phase {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "clean" => Ok(Phase::Clean),
            "validate" => Ok(Phase::Validate),
            "compile" => Ok(Phase::Compile),
            "test" => Ok(Phase::Test),
            "package" => Ok(Phase::Package),
            "verify" => Ok(Phase::Verify),
            "install" => Ok(Phase::Install),
            "deploy" => Ok(Phase::Deploy),
            _ => Err(anyhow::anyhow!("unknown lifecycle phase: {s}")),
        }
    }
}

impl std::fmt::Display for Phase {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

impl Phase {
    /// Return the canonical lowercase phase name (e.g. `"compile"`).
    ///
    /// This is the stable string used for plugin binding lookups and is
    /// independent of the `Debug` representation.
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Phase::Clean => "clean",
            Phase::Validate => "validate",
            Phase::Compile => "compile",
            Phase::Test => "test",
            Phase::Package => "package",
            Phase::Verify => "verify",
            Phase::Install => "install",
            Phase::Deploy => "deploy",
        }
    }

    /// The canonical ordered list of default lifecycle phases (excludes `Clean`).
    fn all_default() -> Vec<Phase> {
        vec![
            Phase::Validate,
            Phase::Compile,
            Phase::Test,
            Phase::Package,
            Phase::Verify,
            Phase::Install,
            Phase::Deploy,
        ]
    }
}

/// Returns all phases in order up to and including the given phase.
/// If the phase is [`Phase::Clean`], returns only `[Clean]`.
#[must_use]
pub fn phases_up_to(phase: &Phase) -> Vec<Phase> {
    if *phase == Phase::Clean {
        return vec![Phase::Clean];
    }
    let all = Phase::all_default();
    // `Phase` is a closed enum: every non-Clean variant is present in `all_default()`,
    // so `position` always finds the phase.
    let pos = all.iter().position(|p| p == phase);
    debug_assert!(pos.is_some(), "Phase {phase:?} not found in all_default()");
    let end = pos.map_or(0, |i| i + 1);
    all.into_iter().take(end).collect()
}

/// Context shared across build phases.
#[non_exhaustive]
pub struct BuildContext {
    pub pom: Pom,
    pub work_dir: PathBuf,
    pub local_repo: PathBuf,
    pub properties: HashMap<String, String>,
    pub plugin_registry: PluginRegistry,
}

impl BuildContext {
    #[must_use]
    pub fn new(
        pom: Pom,
        work_dir: PathBuf,
        local_repo: PathBuf,
        properties: HashMap<String, String>,
    ) -> Self {
        BuildContext {
            pom,
            work_dir,
            local_repo,
            properties,
            plugin_registry: PluginRegistry::default(),
        }
    }

    #[must_use]
    pub fn with_plugin_registry(
        pom: Pom,
        work_dir: PathBuf,
        local_repo: PathBuf,
        properties: HashMap<String, String>,
        plugin_registry: PluginRegistry,
    ) -> Self {
        BuildContext {
            pom,
            work_dir,
            local_repo,
            properties,
            plugin_registry,
        }
    }
}

/// Execute a single build phase.
///
/// After the built-in phase logic runs, any plugin goals bound to this phase
/// (via the [`PluginRegistry`] in the context) are executed in declaration order.
///
/// # Errors
///
/// Returns an error if the phase fails (e.g. compilation error, I/O error)
/// or if a plugin goal returns an error.
pub fn execute_phase(phase: &Phase, context: &BuildContext) -> Result<()> {
    match phase {
        Phase::Clean => {
            let target_dir = context.work_dir.join("target");
            if target_dir.exists() {
                std::fs::remove_dir_all(&target_dir)?;
                println!("[INFO] Deleted {}", target_dir.display());
            }
            println!("[INFO] Clean step complete");
        }
        Phase::Validate => {
            println!("[INFO] Validating project...");
            validate_pom(&context.pom)?;
            println!("[INFO] Project is valid");
        }
        Phase::Compile => {
            println!("[INFO] Compiling sources...");
            crate::compiler::compile_sources(context)?;
        }
        Phase::Test => {
            println!("[INFO] Running tests...");
            let deps = crate::dependency::resolve_dependencies(
                &context.pom,
                &context.local_repo,
                &context.properties,
            )?;
            let classpath: Vec<PathBuf> = deps.iter().map(|d| d.path.clone()).collect();
            crate::compiler::compile_test_sources(context, &classpath)?;
            crate::test_runner::run_junit_tests(context, &deps)?;
        }
        Phase::Package => {
            println!("[INFO] Packaging...");
            crate::packager::create_jar(context)?;
        }
        Phase::Verify => {
            println!("[INFO] Verifying...");
            // Placeholder - no extra verification logic by default
            println!("[INFO] Built-in verification complete");
        }
        Phase::Install => {
            println!("[INFO] Installing to local repository...");
            let jar_path = context.work_dir.join("target").join(format!(
                "{}-{}.jar",
                context.pom.artifact_id,
                context.pom.effective_version()
            ));
            if jar_path.exists() {
                crate::repository::install_artifact(&context.local_repo, &context.pom, &jar_path)?;
            } else {
                println!(
                    "[WARNING] JAR not found at {}; skipping install",
                    jar_path.display()
                );
            }
        }
        Phase::Deploy => {
            println!("[WARNING] Deploy phase is not implemented in this version of pman.");
        }
    }

    // Run any plugin goals bound to this phase.
    context
        .plugin_registry
        .execute_phase_plugins(phase.as_str(), context)
        .with_context(|| format!("plugin execution failed during {phase} phase"))?;

    Ok(())
}

fn validate_pom(pom: &Pom) -> Result<()> {
    if pom.artifact_id.is_empty() {
        anyhow::bail!("POM validation failed: artifactId is required");
    }
    if pom.effective_group_id().is_empty() {
        anyhow::bail!("POM validation failed: groupId is required");
    }
    if pom.effective_version().is_empty() {
        anyhow::bail!("POM validation failed: version is required");
    }
    Ok(())
}

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

    #[test]
    fn test_phases_up_to_compile() {
        let phases = phases_up_to(&Phase::Compile);
        assert_eq!(phases, vec![Phase::Validate, Phase::Compile]);
    }

    #[test]
    fn test_phases_up_to_package() {
        let phases = phases_up_to(&Phase::Package);
        assert_eq!(
            phases,
            vec![Phase::Validate, Phase::Compile, Phase::Test, Phase::Package]
        );
    }

    #[test]
    fn test_phases_up_to_install() {
        let phases = phases_up_to(&Phase::Install);
        assert_eq!(
            phases,
            vec![
                Phase::Validate,
                Phase::Compile,
                Phase::Test,
                Phase::Package,
                Phase::Verify,
                Phase::Install,
            ]
        );
    }

    #[test]
    fn test_phases_up_to_clean() {
        let phases = phases_up_to(&Phase::Clean);
        assert_eq!(phases, vec![Phase::Clean]);
    }

    #[test]
    fn test_phases_up_to_deploy() {
        let phases = phases_up_to(&Phase::Deploy);
        assert_eq!(
            phases,
            vec![
                Phase::Validate,
                Phase::Compile,
                Phase::Test,
                Phase::Package,
                Phase::Verify,
                Phase::Install,
                Phase::Deploy,
            ]
        );
    }

    #[test]
    fn test_phase_from_str() {
        assert_eq!("compile".parse::<Phase>().unwrap(), Phase::Compile);
        assert_eq!("PACKAGE".parse::<Phase>().unwrap(), Phase::Package);
        assert!("unknown".parse::<Phase>().is_err());
    }

    #[test]
    fn test_phase_as_str() {
        assert_eq!(Phase::Clean.as_str(), "clean");
        assert_eq!(Phase::Validate.as_str(), "validate");
        assert_eq!(Phase::Compile.as_str(), "compile");
        assert_eq!(Phase::Test.as_str(), "test");
        assert_eq!(Phase::Package.as_str(), "package");
        assert_eq!(Phase::Verify.as_str(), "verify");
        assert_eq!(Phase::Install.as_str(), "install");
        assert_eq!(Phase::Deploy.as_str(), "deploy");
    }

    #[test]
    fn test_phase_display() {
        assert_eq!(format!("{}", Phase::Compile), "compile");
        assert_eq!(format!("{}", Phase::Package), "package");
    }
}
