use anyhow::{Context, Result};
use std::io::Write;
use std::path::PathBuf;
use walkdir::WalkDir;
use zip::write::FileOptions;
use zip::ZipWriter;

use crate::lifecycle::BuildContext;

/// Create a JAR file from compiled classes.
/// Returns the path to the created JAR.
///
/// # Errors
///
/// Returns an error if the target directory cannot be created, the JAR file
/// cannot be written, or any class file cannot be read.
pub fn create_jar(context: &BuildContext) -> Result<PathBuf> {
    let build = context.pom.effective_build();
    let classes_dir = context.work_dir.join(&build.output_directory);
    let target_dir = context.work_dir.join("target");

    std::fs::create_dir_all(&target_dir).with_context(|| {
        format!(
            "Failed to create target directory: {}",
            target_dir.display()
        )
    })?;

    let artifact_id = &context.pom.artifact_id;
    let version = context.pom.effective_version();
    let jar_name = format!("{artifact_id}-{version}.jar");
    let jar_path = target_dir.join(&jar_name);

    let jar_file = std::fs::File::create(&jar_path)
        .with_context(|| format!("Failed to create JAR file: {}", jar_path.display()))?;

    let mut zip = ZipWriter::new(jar_file);
    let options = FileOptions::default().compression_method(zip::CompressionMethod::Deflated);

    // Write META-INF/MANIFEST.MF
    zip.start_file("META-INF/MANIFEST.MF", options)
        .context("Failed to start MANIFEST.MF in JAR")?;
    let group_id = context.pom.effective_group_id();
    let manifest = format!(
        "Manifest-Version: 1.0\nCreated-By: pman\nBuilt-By: pman\n\
         Build-Jdk: unknown\nArtifact-Id: {artifact_id}\nGroup-Id: {group_id}\nVersion: {version}\n"
    );
    zip.write_all(manifest.as_bytes())
        .context("Failed to write MANIFEST.MF")?;

    // Add class files
    if classes_dir.exists() {
        for entry in WalkDir::new(&classes_dir)
            .into_iter()
            .filter_map(|e| e.ok())
            .filter(|e| e.file_type().is_file())
        {
            let path = entry.path();
            let relative = path
                .strip_prefix(&classes_dir)
                .context("Failed to strip prefix from class path")?;
            let zip_path = relative.to_string_lossy().replace('\\', "/");

            zip.start_file(&zip_path, options)
                .with_context(|| format!("Failed to add {zip_path} to JAR"))?;
            let class_bytes = std::fs::read(path)
                .with_context(|| format!("Failed to read {}", path.display()))?;
            zip.write_all(&class_bytes)
                .with_context(|| format!("Failed to write {zip_path} to JAR"))?;
        }
    } else {
        println!(
            "[WARNING] Classes directory {} does not exist; JAR will only contain MANIFEST.MF",
            classes_dir.display()
        );
    }

    zip.finish().context("Failed to finalize JAR file")?;

    println!("[INFO] Created JAR: {}", jar_path.display());
    Ok(jar_path)
}
