use crate::release_common::{
    CmdRunner, ReleaseChannel, ReleaseContext, cargo_xtask, check_public_updater_endpoint,
    default_repo, default_site_base_url, default_target, gh, git, npm,
};
use anyhow::Result;

/// Verify public release assets, commit updater metadata, build the website, and optionally check the public endpoint.
#[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,

    /// Commit the selected updater JSON after verification.
    #[arg(long)]
    commit: bool,

    /// Push main after committing updater metadata. Requires --commit.
    #[arg(long, requires = "commit")]
    push: bool,

    /// Skip local website check/build.
    #[arg(long)]
    skip_website_build: bool,

    /// Check the public updater endpoint after deployment.
    #[arg(long)]
    check_public: 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);

        download_public_installer(&runner, &ctx)?;
        verify_with_downloaded_installer(&runner, &ctx)?;

        if self.commit {
            commit_updater_metadata(&runner, &ctx)?;
            if self.push {
                let mut cmd = git();
                cmd.arg("push").arg("origin").arg("main");
                runner.run(cmd, "pushing updater metadata")?;
            }
        }

        if !self.skip_website_build {
            build_website(&runner, &ctx)?;
        }

        if self.check_public && !self.dry_run {
            check_public_updater_endpoint(&ctx)?;
        } else {
            println!(
                "deploy website/dist for {} before public endpoint verification",
                ctx.site_base_url
            );
            println!("public updater endpoint: {}", ctx.updater_endpoint);
        }

        Ok(0)
    }
}

fn download_public_installer(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    if !runner.dry_run() {
        std::fs::create_dir_all(ctx.release_check_dir())?;
    }

    let mut cmd = gh();
    cmd.arg("release")
        .arg("download")
        .arg(&ctx.tag)
        .arg("--repo")
        .arg(&ctx.repo)
        .arg("--pattern")
        .arg(&ctx.installer_name)
        .arg("--dir")
        .arg(ctx.release_check_dir())
        .arg("--clobber");
    runner.run(cmd, "downloading public installer")
}

fn verify_with_downloaded_installer(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    let downloaded_installer = ctx.release_check_dir().join(&ctx.installer_name);
    let mut cmd = cargo_xtask();
    cmd.arg("verify-alcom-updater-json")
        .arg("--installer")
        .arg(downloaded_installer)
        .arg("--json")
        .arg(&ctx.updater_json);
    runner.run(cmd, "verifying updater JSON against public installer")
}

fn commit_updater_metadata(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    let mut cmd = git();
    cmd.arg("add").arg(&ctx.updater_json);
    runner.run(cmd, "staging updater metadata")?;

    let mut cmd = git();
    cmd.arg("status").arg("--short");
    runner.run(cmd, "showing staged updater metadata")?;

    let mut cmd = git();
    cmd.arg("commit").arg("-m").arg(format!(
        "release: publish ALCOMD3 {} updater metadata",
        ctx.version
    ));
    runner.run(cmd, "committing updater metadata")
}

fn build_website(runner: &CmdRunner, ctx: &ReleaseContext) -> Result<()> {
    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")
}
