use crate::release_common::{
    CmdRunner, ReleaseChannel, ReleaseContext, cargo_xtask, copy_release_artifacts, default_repo,
    default_site_base_url, default_target, ensure_path_exists, ensure_release_notes_ready, npm,
    run_sign_installer, verify_release_assets,
};
use anyhow::Result;
use std::path::PathBuf;

/// Validate, build, sign, generate updater JSON, and collect local release artifacts.
#[derive(clap::Parser)]
pub struct Command {
    /// Release version, for example 2.0.1 or 2.1.0-beta.1.
    #[arg(long)]
    version: String,

    /// Release channel.
    #[arg(long, value_enum, default_value_t = ReleaseChannel::Stable)]
    channel: ReleaseChannel,

    /// GitHub repository in OWNER/REPO form.
    #[arg(long, default_value_t = default_repo())]
    repo: String,

    /// Public website base URL.
    #[arg(long, default_value_t = default_site_base_url())]
    site_base_url: String,

    /// Build target.
    #[arg(long, default_value_t = default_target())]
    target: String,

    /// Updater private key loader file or directory used when signing environment variables are not already set.
    #[arg(
        long = "key-loader",
        alias = "key-script",
        default_value = "artifacts/alcomd3-updater-key"
    )]
    key_loader: PathBuf,

    /// Skip cargo/npm validation commands.
    #[arg(long)]
    skip_validation: bool,

    /// Print planned commands without executing them.
    #[arg(long)]
    dry_run: bool,
}

impl crate::Command for Command {
    fn run(self) -> Result<i32> {
        let ctx = ReleaseContext::new(
            self.version,
            self.channel,
            Some(self.repo),
            Some(self.site_base_url),
            Some(self.target),
        )?;
        let runner = CmdRunner::new(self.dry_run);

        if !self.dry_run {
            ensure_release_notes_ready(&ctx)?;
        }

        if !self.skip_validation {
            run_validation(&runner, &ctx)?;
        }

        run_build_and_bundle(&runner, &ctx)?;
        if !self.dry_run {
            ensure_path_exists(&ctx.installer, "installer")?;
            ensure_path_exists(&ctx.zip, "installer zip")?;
        }

        let key_loader = ctx.workspace_root.join(self.key_loader);
        run_sign_installer(&ctx, &runner, &key_loader)?;

        run_updater_json(&runner, &ctx)?;

        if !self.dry_run {
            verify_release_assets(&ctx)?;
        }
        copy_release_artifacts(&ctx, self.dry_run)?;

        println!(
            "release artifacts are ready: {}",
            ctx.artifact_dir().display()
        );
        Ok(0)
    }
}

fn run_validation(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    let mut cargo_check = crate::release_common::git();
    cargo_check
        .arg("--version")
        .current_dir(&ctx.workspace_root);
    runner.run(cargo_check, "checking git command availability")?;

    let mut cmd = std::process::Command::new("cargo");
    cmd.arg("check").current_dir(&ctx.workspace_root);
    runner.run(cmd, "cargo check")?;

    let mut cmd = std::process::Command::new("cargo");
    cmd.arg("test").current_dir(&ctx.workspace_root);
    runner.run(cmd, "cargo test")?;

    let mut cmd = npm();
    cmd.arg("run")
        .arg("lint")
        .current_dir(ctx.workspace_root.join("vrc-get-gui"));
    runner.run(cmd, "GUI lint")?;

    let mut cmd = npm();
    cmd.arg("run")
        .arg("build")
        .current_dir(ctx.workspace_root.join("vrc-get-gui"));
    runner.run(cmd, "GUI build")?;

    let mut cmd = npm();
    cmd.arg("run")
        .arg("check")
        .current_dir(ctx.workspace_root.join("website"));
    runner.run(cmd, "website check")?;

    let mut cmd = npm();
    cmd.arg("run")
        .arg("build")
        .current_dir(ctx.workspace_root.join("website"));
    runner.run(cmd, "website build")?;

    Ok(())
}

fn run_build_and_bundle(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    let mut cmd = cargo_xtask();
    cmd.arg("build-alcom")
        .arg("--release")
        .arg("--target")
        .arg(&ctx.target)
        .current_dir(&ctx.workspace_root);
    runner.run(cmd, "building ALCOMD3")?;

    let mut cmd = cargo_xtask();
    cmd.arg("bundle-alcom")
        .arg("--release")
        .arg("--target")
        .arg(&ctx.target)
        .arg("--bundles")
        .arg("setup-exe,setup-exe-zip")
        .current_dir(&ctx.workspace_root);
    runner.run(cmd, "bundling ALCOMD3")?;

    Ok(())
}

fn run_updater_json(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    let mut cmd = cargo_xtask();
    cmd.arg("alcom-updater-json")
        .arg("--assets")
        .arg(&ctx.setup_dir)
        .arg("--version")
        .arg(&ctx.version)
        .arg(&ctx.updater_json)
        .current_dir(&ctx.workspace_root);
    runner.run(cmd, "generating updater JSON")?;

    let mut cmd = cargo_xtask();
    cmd.arg("verify-alcom-updater-json")
        .arg("--installer")
        .arg(&ctx.installer)
        .arg("--json")
        .arg(&ctx.updater_json)
        .current_dir(&ctx.workspace_root);
    runner.run(cmd, "verifying updater JSON")?;

    Ok(())
}
