// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

//! Gets the Github workflow id for a given commit hash

use flowey::node::prelude::*;

flowey_request! {
    pub struct Request {
        pub github_commit_hash: ReadVar<String>,
        pub repo_path: ReadVar<PathBuf>,
        pub pipeline_name: String,
        pub gh_workflow: WriteVar<GithubWorkflow>,
    }
}

#[derive(Serialize, Deserialize, Clone)]
pub struct GithubWorkflow {
    pub id: String,
    pub commit: String,
}

new_simple_flow_node!(struct Node);

impl SimpleFlowNode for Node {
    type Request = Request;

    fn imports(ctx: &mut ImportCtx<'_>) {
        ctx.import::<crate::use_gh_cli::Node>();
    }

    fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
        let Request {
            repo_path,
            github_commit_hash,
            gh_workflow,
            pipeline_name,
        } = request;

        let pipeline_name = pipeline_name.clone();

        let gh_cli = ctx.reqv(crate::use_gh_cli::Request::Get);

        ctx.emit_rust_step("get action id", |ctx| {
            let gh_workflow = gh_workflow.claim(ctx);
            let github_commit_hash = github_commit_hash.claim(ctx);
            let repo_path = repo_path.claim(ctx);
            let pipeline_name = pipeline_name.clone();
            let gh_cli = gh_cli.claim(ctx);

            move |rt| {
                let github_commit_hash = rt.read(github_commit_hash);
                let sh = xshell::Shell::new()?;
                let repo_path = rt.read(repo_path);
                let gh_cli = rt.read(gh_cli);

                sh.change_dir(repo_path);

                // Fetches the CI build workflow id for a given commit hash
                let get_action_id = |commit: String| -> Option<String> {
                    let output = xshell::cmd!(
                        sh,
                        "{gh_cli} run list
                        --commit {commit}
                        -w {pipeline_name}
                        -s success
                        -L 1
                        --json databaseId
                        --jq .[].databaseId"
                    )
                    .read();

                    match output {
                        Ok(output) if output.trim().is_empty() => None,
                        Ok(output) => Some(output),
                        Err(e) => {
                            println!("Failed to get action id for commit {}: {}", commit, e);
                            None
                        }
                    }
                };

                let mut github_commit_hash = github_commit_hash.clone();
                let mut action_id = get_action_id(github_commit_hash.clone());
                let mut loop_count = 0;

                // CI may not have finished the build for the merge base, so loop through commits
                // until we find a finished build or fail after 5 attempts
                while action_id.is_none() {
                    println!(
                        "Unable to get action id for commit {}, trying again",
                        github_commit_hash
                    );

                    if loop_count > 4 {
                        anyhow::bail!("Failed to get action id after 5 attempts");
                    }

                    github_commit_hash =
                        xshell::cmd!(sh, "git rev-parse {github_commit_hash}^").read()?;
                    action_id = get_action_id(github_commit_hash.clone());

                    loop_count += 1;
                }

                // We have an action id or we would've bailed in the loop above
                let id = action_id.context("failed to get action id")?;

                println!("Got action id {id}, commit {github_commit_hash}");
                rt.write(
                    gh_workflow,
                    &GithubWorkflow {
                        id,
                        commit: github_commit_hash,
                    },
                );

                Ok(())
            }
        });

        Ok(())
    }
}
