use gh_workflow::{Container, Event, Port, Push, Run, Step, Use, Workflow};
use indoc::indoc;

use crate::tasks::workflows::runners::{self, Platform};
use crate::tasks::workflows::steps::{
    self, CommonJobConditions, FluentBuilder as _, NamedJob, dependant_job, named, use_clang,
};
use crate::tasks::workflows::vars;

pub(crate) fn deploy_collab() -> Workflow {
    let style = style();
    let tests = tests(&[&style]);
    let publish = publish(&[&style, &tests]);
    let deploy = deploy(&[&publish]);

    named::workflow()
        .on(Event::default().push(Push::default().add_tag("collab-production")))
        .add_env(("DOCKER_BUILDKIT", "1"))
        .add_job(style.name, style.job)
        .add_job(tests.name, tests.job)
        .add_job(publish.name, publish.job)
        .add_job(deploy.name, deploy.job)
}

fn style() -> NamedJob {
    named::job(use_clang(
        dependant_job(&[])
            .name("Check formatting and Clippy lints")
            .with_repository_owner_guard()
            .runs_on(runners::LINUX_XL)
            .add_step(steps::checkout_repo().with_full_history())
            .add_step(steps::setup_cargo_config(Platform::Linux))
            .add_step(steps::cache_rust_dependencies_namespace())
            .map(steps::install_linux_dependencies)
            .add_step(steps::cargo_fmt())
            .add_step(steps::clippy(Platform::Linux, None)),
    ))
}

fn tests(deps: &[&NamedJob]) -> NamedJob {
    fn run_collab_tests() -> Step<Run> {
        named::bash("cargo nextest run --package collab --no-fail-fast")
    }

    named::job(use_clang(
        dependant_job(deps)
            .name("Run tests")
            .runs_on(runners::LINUX_XL)
            .add_service(
                "postgres",
                Container::new("postgres:15")
                    .add_env(("POSTGRES_HOST_AUTH_METHOD", "trust"))
                    .ports(vec![Port::Name("5432:5432".into())])
                    .options(
                        "--health-cmd pg_isready \
                         --health-interval 500ms \
                         --health-timeout 5s \
                         --health-retries 10",
                    ),
            )
            .add_step(steps::checkout_repo().with_full_history())
            .add_step(steps::setup_cargo_config(Platform::Linux))
            .add_step(steps::cache_rust_dependencies_namespace())
            .map(steps::install_linux_dependencies)
            .add_step(steps::cargo_install_nextest())
            .add_step(steps::clear_target_dir_if_large(Platform::Linux))
            .add_step(run_collab_tests()),
    ))
}

fn publish(deps: &[&NamedJob]) -> NamedJob {
    fn install_doctl() -> Step<Use> {
        named::uses("digitalocean", "action-doctl", "v2")
            .add_with(("token", vars::DIGITALOCEAN_ACCESS_TOKEN))
    }

    fn sign_into_registry() -> Step<Run> {
        named::bash("doctl registry login")
    }

    fn build_docker_image() -> Step<Run> {
        named::bash(indoc! {r#"
            docker build -f Dockerfile-collab \
              --build-arg "GITHUB_SHA=$GITHUB_SHA" \
              --tag "registry.digitalocean.com/zed/collab:$GITHUB_SHA" \
              .
        "#})
    }

    fn publish_docker_image() -> Step<Run> {
        named::bash(r#"docker push "registry.digitalocean.com/zed/collab:${GITHUB_SHA}""#)
    }

    fn prune_docker_system() -> Step<Run> {
        named::bash("docker system prune --filter 'until=72h' -f")
    }

    named::job(
        dependant_job(deps)
            .name("Publish collab server image")
            .runs_on(runners::LINUX_XL)
            .add_step(install_doctl())
            .add_step(sign_into_registry())
            .add_step(steps::checkout_repo())
            .add_step(build_docker_image())
            .add_step(publish_docker_image())
            .add_step(prune_docker_system()),
    )
}

fn deploy(deps: &[&NamedJob]) -> NamedJob {
    fn install_doctl() -> Step<Use> {
        named::uses("digitalocean", "action-doctl", "v2")
            .add_with(("token", vars::DIGITALOCEAN_ACCESS_TOKEN))
    }

    fn sign_into_kubernetes() -> Step<Run> {
        named::bash(
            r#"doctl kubernetes cluster kubeconfig save --expiry-seconds 600 "$CLUSTER_NAME""#,
        )
        .add_env(("CLUSTER_NAME", vars::CLUSTER_NAME))
    }

    fn start_rollout() -> Step<Run> {
        named::bash(indoc! {r#"
            set -eu
            if [[ $GITHUB_REF_NAME = "collab-production" ]]; then
              export ZED_KUBE_NAMESPACE=production
              export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=10
              export ZED_API_LOAD_BALANCER_SIZE_UNIT=2
            elif [[ $GITHUB_REF_NAME = "collab-staging" ]]; then
              export ZED_KUBE_NAMESPACE=staging
              export ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT=1
              export ZED_API_LOAD_BALANCER_SIZE_UNIT=1
            else
              echo "cowardly refusing to deploy from an unknown branch"
              exit 1
            fi

            echo "Deploying collab:$GITHUB_SHA to $ZED_KUBE_NAMESPACE"

            source script/lib/deploy-helpers.sh
            export_vars_for_environment "$ZED_KUBE_NAMESPACE"

            ZED_DO_CERTIFICATE_ID="$(doctl compute certificate list --format ID --no-header)"
            export ZED_DO_CERTIFICATE_ID
            export ZED_IMAGE_ID="registry.digitalocean.com/zed/collab:${GITHUB_SHA}"

            export ZED_SERVICE_NAME=collab
            export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_COLLAB_LOAD_BALANCER_SIZE_UNIT
            export DATABASE_MAX_CONNECTIONS=850
            envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
            kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch
            echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"

            export ZED_SERVICE_NAME=api
            export ZED_LOAD_BALANCER_SIZE_UNIT=$ZED_API_LOAD_BALANCER_SIZE_UNIT
            export DATABASE_MAX_CONNECTIONS=60
            envsubst < crates/collab/k8s/collab.template.yml | kubectl apply -f -
            kubectl -n "$ZED_KUBE_NAMESPACE" rollout status "deployment/$ZED_SERVICE_NAME" --watch
            echo "deployed ${ZED_SERVICE_NAME} to ${ZED_KUBE_NAMESPACE}"
        "#})
    }

    named::job(
        dependant_job(deps)
            .name("Deploy new server image")
            .runs_on(runners::LINUX_XL)
            .add_step(steps::checkout_repo())
            .add_step(install_doctl())
            .add_step(sign_into_kubernetes())
            .add_step(start_rollout()),
    )
}
