use gh_workflow::*;

use crate::tasks::workflows::{
    runners,
    steps::{self, FluentBuilder, NamedJob, RepositoryTarget, TokenPermissions, named, use_clang},
    vars::{self, StepOutput, WorkflowInput},
};

pub fn autofix_pr() -> Workflow {
    let pr_number = WorkflowInput::string("pr_number", None);
    let run_clippy = WorkflowInput::bool("run_clippy", Some(true));
    let run_autofix = run_autofix(&pr_number, &run_clippy);
    let commit_changes = commit_changes(&pr_number, &run_autofix);
    named::workflow()
        .run_name(format!("autofix PR #{pr_number}"))
        .on(Event::default().workflow_dispatch(
            WorkflowDispatch::default()
                .add_input(pr_number.name, pr_number.input())
                .add_input(run_clippy.name, run_clippy.input()),
        ))
        .concurrency(
            Concurrency::new(Expression::new(format!(
                "${{{{ github.workflow }}}}-{pr_number}"
            )))
            .cancel_in_progress(true),
        )
        .add_job(run_autofix.name.clone(), run_autofix.job)
        .add_job(commit_changes.name, commit_changes.job)
}

const PATCH_ARTIFACT_NAME: &str = "autofix-patch";
const PATCH_FILE_PATH: &str = "autofix.patch";

fn upload_patch_artifact() -> Step<Use> {
    Step::new(format!("upload artifact {}", PATCH_ARTIFACT_NAME))
        .uses(
            "actions",
            "upload-artifact",
            "330a01c490aca151604b8cf639adc76d48f6c5d4", // v5
        )
        .add_with(("name", PATCH_ARTIFACT_NAME))
        .add_with(("path", PATCH_FILE_PATH))
        .add_with(("if-no-files-found", "ignore"))
        .add_with(("retention-days", "1"))
}

fn download_patch_artifact() -> Step<Use> {
    named::uses(
        "actions",
        "download-artifact",
        "018cc2cf5baa6db3ef3c5f8a56943fffe632ef53", // v6.0.0
    )
    .add_with(("name", PATCH_ARTIFACT_NAME))
}

fn run_autofix(pr_number: &WorkflowInput, run_clippy: &WorkflowInput) -> NamedJob {
    fn checkout_pr(pr_number: &WorkflowInput) -> Step<Run> {
        named::bash(r#"gh pr checkout "$PR_NUMBER""#)
            .add_env(("PR_NUMBER", pr_number.to_string()))
            .add_env(("GITHUB_TOKEN", vars::GITHUB_TOKEN))
    }

    fn install_cargo_machete() -> Step<Use> {
        steps::taiki_install_action("cargo-machete@0.7.0")
    }

    fn run_cargo_fmt() -> Step<Run> {
        named::bash("cargo fmt --all")
    }

    fn run_cargo_fix() -> Step<Run> {
        named::bash("cargo fix --workspace --allow-dirty --allow-staged")
    }

    fn run_cargo_machete_fix() -> Step<Run> {
        named::bash("cargo machete --fix")
    }

    fn run_clippy_fix() -> Step<Run> {
        named::bash("cargo clippy --workspace --fix --allow-dirty --allow-staged")
    }

    fn run_prettier_fix() -> Step<Run> {
        named::bash("./script/prettier --write")
    }

    fn create_patch() -> Step<Run> {
        named::bash(indoc::indoc! {r#"
            if git diff --quiet; then
                echo "No changes to commit"
                echo "has_changes=false" >> "$GITHUB_OUTPUT"
            else
                git diff > autofix.patch
                echo "has_changes=true" >> "$GITHUB_OUTPUT"
            fi
        "#})
        .id("create-patch")
    }

    named::job(use_clang(
        Job::default()
            .runs_on(runners::LINUX_DEFAULT)
            .outputs([(
                "has_changes".to_owned(),
                "${{ steps.create-patch.outputs.has_changes }}".to_owned(),
            )])
            .add_step(steps::checkout_repo())
            .add_step(checkout_pr(pr_number))
            .add_step(steps::setup_cargo_config(runners::Platform::Linux))
            .add_step(steps::cache_rust_dependencies_namespace())
            .map(steps::install_linux_dependencies)
            .add_step(steps::setup_pnpm())
            .add_step(install_cargo_machete().if_condition(Expression::new(run_clippy.to_string())))
            .add_step(run_cargo_fix().if_condition(Expression::new(run_clippy.to_string())))
            .add_step(run_cargo_machete_fix().if_condition(Expression::new(run_clippy.to_string())))
            .add_step(run_clippy_fix().if_condition(Expression::new(run_clippy.to_string())))
            .add_step(run_prettier_fix())
            .add_step(run_cargo_fmt())
            .add_step(create_patch())
            .add_step(upload_patch_artifact())
            .add_step(steps::cleanup_cargo_config(runners::Platform::Linux)),
    ))
}

fn commit_changes(pr_number: &WorkflowInput, autofix_job: &NamedJob) -> NamedJob {
    fn checkout_pr(pr_number: &WorkflowInput, token: &StepOutput) -> Step<Run> {
        named::bash(r#"gh pr checkout "$PR_NUMBER""#)
            .add_env(("PR_NUMBER", pr_number.to_string()))
            .add_env(("GITHUB_TOKEN", token))
    }

    fn apply_patch() -> Step<Run> {
        named::bash("git apply autofix.patch")
    }

    fn commit_and_push(token: &StepOutput) -> Step<Run> {
        named::bash(indoc::indoc! {r#"
            git commit -am "Autofix"
            git push
        "#})
        .add_env(("GIT_COMMITTER_NAME", "Zed Zippy"))
        .add_env((
            "GIT_COMMITTER_EMAIL",
            "234243425+zed-zippy[bot]@users.noreply.github.com",
        ))
        .add_env(("GIT_AUTHOR_NAME", "Zed Zippy"))
        .add_env((
            "GIT_AUTHOR_EMAIL",
            "234243425+zed-zippy[bot]@users.noreply.github.com",
        ))
        .add_env(("GITHUB_TOKEN", token))
    }

    let (authenticate, token) = steps::authenticate_as_zippy()
        .for_repository(RepositoryTarget::current())
        .with_permissions([
            (TokenPermissions::Contents, Level::Write),
            (TokenPermissions::Workflows, Level::Write),
        ])
        .into();

    named::job(
        Job::default()
            .runs_on(runners::LINUX_SMALL)
            .needs(vec![autofix_job.name.clone()])
            .cond(Expression::new(format!(
                "needs.{}.outputs.has_changes == 'true'",
                autofix_job.name
            )))
            .add_step(authenticate)
            .add_step(steps::checkout_repo().with_token(&token))
            .add_step(checkout_pr(pr_number, &token))
            .add_step(download_patch_artifact())
            .add_step(apply_patch())
            .add_step(commit_and_push(&token)),
    )
}
