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

use pman::lifecycle::{execute_phase, phases_up_to, BuildContext, Phase};
use pman::plugin::{EchoPlugin, PluginRegistry};
use pman::pom::parse_pom;
use pman::repository::local_repo_path;

/// pman - A Rust port of Apache Maven
#[derive(Parser, Debug)]
#[command(name = "pman", about = "A Rust port of Apache Maven", version)]
struct Cli {
    /// Goals/phases to execute (e.g. clean compile package)
    #[arg(required = true)]
    goals: Vec<String>,

    /// Path to the POM file
    #[arg(short = 'f', long = "file", default_value = "pom.xml")]
    file: PathBuf,

    /// Set a system property (e.g. -Dkey=value)
    #[arg(short = 'D', value_name = "PROPERTY")]
    properties: Vec<String>,
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    // Parse user-provided properties
    let mut properties: HashMap<String, String> = HashMap::new();
    for prop in &cli.properties {
        if let Some((key, value)) = prop.split_once('=') {
            properties.insert(key.to_string(), value.to_string());
        } else {
            properties.insert(prop.clone(), "true".to_string());
        }
    }

    // Locate and parse the POM
    let pom_path = if cli.file.is_absolute() {
        cli.file.clone()
    } else {
        std::env::current_dir()
            .context("Failed to get current directory")?
            .join(&cli.file)
    };

    println!("[INFO] Reading POM from: {}", pom_path.display());
    let pom = parse_pom(&pom_path)
        .with_context(|| format!("Failed to parse POM: {}", pom_path.display()))?;

    let work_dir = pom_path
        .parent()
        .unwrap_or_else(|| std::path::Path::new("."))
        .to_path_buf();

    let local_repo = local_repo_path();
    std::fs::create_dir_all(&local_repo)
        .with_context(|| format!("Failed to create local repo: {}", local_repo.display()))?;

    let context = build_context(pom, work_dir, local_repo, properties);

    println!(
        "[INFO] Building {}:{} ({})",
        context.pom.effective_group_id(),
        context.pom.artifact_id,
        context.pom.effective_version()
    );

    // Collect all phases to run across all requested goals, in order, deduplicated
    let mut all_phases: Vec<Phase> = Vec::new();
    for goal in &cli.goals {
        match Phase::from_str(goal) {
            Ok(phase) => {
                for p in phases_up_to(&phase) {
                    if !all_phases.contains(&p) {
                        all_phases.push(p);
                    }
                }
            }
            Err(_) => {
                eprintln!("[WARNING] Unknown goal: {goal}. Skipping.");
            }
        }
    }

    for phase in &all_phases {
        execute_phase(phase, &context).with_context(|| format!("Phase {phase:?} failed"))?;
    }

    println!("[INFO] BUILD SUCCESS");
    Ok(())
}

/// Build a [`BuildContext`] with a [`PluginRegistry`] populated from POM plugin
/// declarations. Built-in plugin goals (e.g. `EchoPlugin`) are registered
/// automatically.
fn build_context(
    pom: pman::pom::Pom,
    work_dir: PathBuf,
    local_repo: PathBuf,
    properties: HashMap<String, String>,
) -> BuildContext {
    let mut registry = PluginRegistry::new();

    // Register built-in plugin goals.
    registry.register_goal(
        "org.pman:pman-echo-plugin:echo".to_string(),
        Box::new(EchoPlugin),
    );

    // Populate bindings from POM <build><plugins> declarations.
    let build = pom.effective_build();
    for plugin in &build.plugins.items {
        // Validate plugin coordinates before constructing the prefix.
        if plugin.group_id.is_empty() || plugin.artifact_id.is_empty() {
            eprintln!(
                "[WARNING] Plugin declaration with missing coordinates (groupId='{}', artifactId='{}'); skipping all executions",
                plugin.group_id,
                plugin.artifact_id
            );
            continue;
        }

        let prefix = format!("{}:{}", plugin.group_id, plugin.artifact_id);

        for execution in &plugin.executions.items {
            let exec_label = if execution.id.is_empty() {
                "(unnamed)"
            } else {
                &execution.id
            };

            // Skip executions with an empty phase.
            if execution.phase.is_empty() {
                eprintln!(
                    "[WARNING] Plugin {prefix}: execution '{exec_label}' has no <phase>; skipping binding"
                );
                continue;
            }

            // Skip executions whose phase is not a recognized lifecycle phase.
            if Phase::from_str(&execution.phase).is_err() {
                eprintln!(
                    "[WARNING] Plugin {prefix}: execution '{exec_label}' has unknown phase '{}'; skipping binding",
                    execution.phase
                );
                continue;
            }

            // Skip executions with no declared goals.
            if execution.goals.items.is_empty() {
                eprintln!(
                    "[WARNING] Plugin {prefix}: execution '{exec_label}' has no <goals>; skipping binding"
                );
                continue;
            }

            // Merge plugin-level configuration with execution-level overrides.
            let mut config = plugin.configuration.entries.clone();
            config.extend(execution.configuration.entries.clone());
            let config = Arc::new(config);

            let phase = execution.phase.clone();
            for goal in &execution.goals.items {
                let trimmed_goal = goal.trim();
                if trimmed_goal.is_empty() {
                    eprintln!(
                        "[WARNING] Plugin {prefix}: execution '{exec_label}' has empty <goal>; skipping binding"
                    );
                    continue;
                }

                let goal_key = format!("{prefix}:{trimmed_goal}");
                registry.add_binding(phase.clone(), goal_key, Arc::clone(&config));
            }
        }
    }

    BuildContext::with_plugin_registry(pom, work_dir, local_repo, properties, registry)
}
