#!/usr/bin/env bash
#########################################################################################
#### gitops-bootstrap.sh — Bootstrap GitOps on Kubernetes with Flux or ArgoCD        ####
#### Install, configure git source, sync applications, and validate deployments      ####
#### Requires: bash 4+, kubectl, git, flux CLI or argocd CLI                         ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.00                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./gitops-bootstrap.sh --install flux --repo git@github.com:org/infra.git      ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################
set -euo pipefail

VERSION="1.00"

# --- ANSI color variables (pre-initialized) ---
RED=""
GREEN=""
YELLOW=""
BLUE=""
CYAN=""
BOLD=""
DIM=""
RESET=""

# --- Defaults ---
RUN_MODE=""
GITOPS_TOOL="${GITOPS_TOOL:-flux}"
GIT_REPO="${GITOPS_REPO:-}"
GIT_BRANCH="${GITOPS_BRANCH:-main}"
GIT_PATH="${GITOPS_PATH:-./clusters/default}"
NAMESPACE="${GITOPS_NAMESPACE:-}"
KUBECONFIG_FILE="${KUBECONFIG:-}"
KUBE_CTX="${KUBE_CONTEXT:-}"
CONFIRM_YES=false
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

# --- State ---
readonly SCRIPT_NAME="${0##*/}"
START_TIME=$(date +%s)

# --- Source name used for flux commands ---
SOURCE_NAME="main"
KUSTOMIZATION_NAME="default"
APP_NAME=""

# --- Color setup ---
setup_colors() {
    if [[ "$COLOR" == "never" ]]; then
        RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
        return
    fi
    if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
        RED='\033[0;31m'
        GREEN='\033[0;32m'
        YELLOW='\033[1;33m'
        BLUE='\033[0;34m'
        CYAN='\033[0;36m'
        BOLD='\033[1m'
        DIM='\033[2m'
        RESET='\033[0m'
    fi
}

# --- Logging ---
log()     { printf "%b\n" "${GREEN}✔${RESET} $*"; }
warn()    { printf "%b\n" "${YELLOW}⚠${RESET} $*" >&2; }
err()     { printf "%b\n" "${RED}✖${RESET} $*" >&2; }
verbose() { [[ "$VERBOSE" == "true" ]] && printf "%b\n" "${DIM}▸ $*${RESET}" >&2; return 0; }
die()     { err "$*"; exit 1; }

section_header() {
    printf "\n%b━━━ %s ━━━%b\n" "${BOLD}${BLUE}" "$1" "${RESET}"
}

field() {
    printf "  %-22s %s\n" "$1:" "$2"
}

field_color() {
    printf "  %-22s %b%s%b\n" "$1:" "$2" "$3" "${RESET}"
}

# --- Resolve namespace default based on tool ---
resolve_namespace() {
    if [[ -z "$NAMESPACE" ]]; then
        if [[ "$GITOPS_TOOL" == "argocd" ]]; then
            NAMESPACE="argocd"
        else
            NAMESPACE="flux-system"
        fi
    fi
}

# --- kubectl wrapper ---
kubectl_cmd() {
    local -a args=("kubectl")
    [[ -n "$KUBECONFIG_FILE" ]] && args+=("--kubeconfig" "$KUBECONFIG_FILE")
    [[ -n "$KUBE_CTX" ]] && args+=("--context" "$KUBE_CTX")
    "${args[@]}" "$@"
}

# --- Dependency checks ---
require_kubectl() {
    command -v kubectl >/dev/null 2>&1 || die "kubectl is required but not found in PATH"
}

require_flux() {
    command -v flux >/dev/null 2>&1 || die "flux CLI is required but not found in PATH"
}

require_argocd() {
    command -v argocd >/dev/null 2>&1 || die "argocd CLI is required but not found in PATH"
}

require_git() {
    command -v git >/dev/null 2>&1 || die "git is required but not found in PATH"
}

# --- Confirm prompt ---
confirm_action() {
    local prompt="${1:-Continue?}"
    if [[ "$CONFIRM_YES" == "true" ]]; then
        return 0
    fi
    printf "%s [y/N] " "$prompt"
    read -r answer
    [[ "$answer" =~ ^[Yy]$ ]] || die "Aborted"
}

# --- Wait for pods ready in namespace ---
wait_for_pods() {
    local ns="$1"
    local timeout="${2:-120}"
    log "Waiting for pods in namespace ${CYAN}${ns}${RESET} (timeout ${timeout}s)"

    local deadline=$(($(date +%s) + timeout))
    while true; do
        local not_ready
        not_ready=$(kubectl_cmd get pods -n "$ns" --no-headers 2>/dev/null \
            | grep -cvE 'Running|Completed|Succeeded' || true)
        if [[ "$not_ready" -eq 0 ]]; then
            local total
            total=$(kubectl_cmd get pods -n "$ns" --no-headers 2>/dev/null | wc -l)
            if [[ "$total" -gt 0 ]]; then
                log "All ${total} pod(s) ready in ${ns}"
                return 0
            fi
        fi
        if [[ $(date +%s) -ge $deadline ]]; then
            warn "Timeout waiting for pods in ${ns}"
            kubectl_cmd get pods -n "$ns" --no-headers 2>/dev/null || true
            return 1
        fi
        sleep 5
    done
}

# ─────────────────────────────────────────────────────────────────────
# Install
# ─────────────────────────────────────────────────────────────────────

do_install_flux() {
    section_header "Installing Flux"
    require_kubectl
    require_flux

    log "Running pre-flight checks"
    verbose "flux check --pre"
    flux check --pre || die "Flux pre-flight checks failed"

    log "Installing Flux components into namespace ${CYAN}${NAMESPACE}${RESET}"
    verbose "flux install --namespace=${NAMESPACE}"
    flux install --namespace="$NAMESPACE"

    wait_for_pods "$NAMESPACE"

    if [[ -n "$GIT_REPO" ]]; then
        log "Configuring GitRepository source"
        verbose "flux create source git ${SOURCE_NAME} --url=${GIT_REPO} --branch=${GIT_BRANCH} --namespace=${NAMESPACE}"
        flux create source git "$SOURCE_NAME" \
            --url="$GIT_REPO" \
            --branch="$GIT_BRANCH" \
            --namespace="$NAMESPACE"

        log "Creating Kustomization"
        verbose "flux create kustomization ${KUSTOMIZATION_NAME} --source=${SOURCE_NAME} --path=${GIT_PATH} --namespace=${NAMESPACE} --prune=true"
        flux create kustomization "$KUSTOMIZATION_NAME" \
            --source="$SOURCE_NAME" \
            --path="$GIT_PATH" \
            --namespace="$NAMESPACE" \
            --prune=true
    fi

    section_header "Flux Installation Summary"
    field "Namespace" "$NAMESPACE"
    field "Git repository" "${GIT_REPO:-not configured}"
    field "Branch" "$GIT_BRANCH"
    field "Path" "$GIT_PATH"
    log "Flux installation complete"
}

do_install_argocd() {
    section_header "Installing Argo CD"
    require_kubectl

    local manifests_url="https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"

    log "Creating namespace ${CYAN}${NAMESPACE}${RESET}"
    kubectl_cmd create namespace "$NAMESPACE" --dry-run=client -o yaml \
        | kubectl_cmd apply -f -

    log "Applying Argo CD manifests"
    verbose "kubectl apply -n ${NAMESPACE} -f ${manifests_url}"
    kubectl_cmd apply -n "$NAMESPACE" -f "$manifests_url"

    wait_for_pods "$NAMESPACE"

    section_header "Argo CD Installation Summary"
    field "Namespace" "$NAMESPACE"
    field "Manifests" "$manifests_url"
    printf "\n"
    log "Retrieve initial admin password:"
    printf "  %bkubectl -n %s get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d%b\n" \
        "${CYAN}" "$NAMESPACE" "${RESET}"
    log "Argo CD installation complete"
}

do_install() {
    case "$GITOPS_TOOL" in
        flux)    do_install_flux ;;
        argocd)  do_install_argocd ;;
        *)       die "Unknown GitOps tool: ${GITOPS_TOOL}. Use 'flux' or 'argocd'." ;;
    esac
}

# ─────────────────────────────────────────────────────────────────────
# Status
# ─────────────────────────────────────────────────────────────────────

do_status_flux() {
    section_header "Flux Status"
    require_kubectl
    require_flux

    log "Sources"
    flux get sources all --namespace="$NAMESPACE" 2>/dev/null || warn "No sources found"

    printf "\n"
    log "Kustomizations"
    flux get kustomizations --namespace="$NAMESPACE" 2>/dev/null || warn "No kustomizations found"

    printf "\n"
    log "Helm releases"
    flux get helmreleases --all-namespaces 2>/dev/null || verbose "No helm releases found"
}

do_status_argocd() {
    section_header "Argo CD Status"
    require_kubectl

    log "Applications"
    kubectl_cmd get applications -n "$NAMESPACE" -o wide 2>/dev/null || warn "No applications found"

    printf "\n"
    log "App Projects"
    kubectl_cmd get appprojects -n "$NAMESPACE" -o wide 2>/dev/null || verbose "No app projects found"
}

do_status() {
    case "$GITOPS_TOOL" in
        flux)    do_status_flux ;;
        argocd)  do_status_argocd ;;
        *)       die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
    esac

    section_header "Pod Status (${NAMESPACE})"
    kubectl_cmd get pods -n "$NAMESPACE" -o wide 2>/dev/null || warn "No pods found"
}

# ─────────────────────────────────────────────────────────────────────
# Add Source
# ─────────────────────────────────────────────────────────────────────

do_add_source_flux() {
    section_header "Adding Git Source (Flux)"
    require_flux

    [[ -z "$GIT_REPO" ]] && die "--repo is required to add a source"

    log "Creating GitRepository source ${CYAN}${SOURCE_NAME}${RESET}"
    verbose "flux create source git ${SOURCE_NAME} --url=${GIT_REPO} --branch=${GIT_BRANCH} --namespace=${NAMESPACE}"
    flux create source git "$SOURCE_NAME" \
        --url="$GIT_REPO" \
        --branch="$GIT_BRANCH" \
        --namespace="$NAMESPACE"

    log "Source added successfully"
    flux get sources git --namespace="$NAMESPACE"
}

do_add_source_argocd() {
    section_header "Adding Git Source (Argo CD)"
    require_argocd

    [[ -z "$GIT_REPO" ]] && die "--repo is required to add a source"

    log "Adding repository ${CYAN}${GIT_REPO}${RESET}"
    verbose "argocd repo add ${GIT_REPO}"
    argocd repo add "$GIT_REPO" || die "Failed to add repository"

    log "Repository added successfully"
}

do_add_source() {
    case "$GITOPS_TOOL" in
        flux)    do_add_source_flux ;;
        argocd)  do_add_source_argocd ;;
        *)       die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
    esac
}

# ─────────────────────────────────────────────────────────────────────
# Sync / Reconcile
# ─────────────────────────────────────────────────────────────────────

do_sync_flux() {
    section_header "Reconciling (Flux)"
    require_flux

    log "Reconciling source git/${SOURCE_NAME}"
    verbose "flux reconcile source git ${SOURCE_NAME} --namespace=${NAMESPACE}"
    flux reconcile source git "$SOURCE_NAME" --namespace="$NAMESPACE"

    log "Reconciling kustomization ${KUSTOMIZATION_NAME}"
    verbose "flux reconcile kustomization ${KUSTOMIZATION_NAME} --namespace=${NAMESPACE}"
    flux reconcile kustomization "$KUSTOMIZATION_NAME" --namespace="$NAMESPACE"

    log "Reconciliation triggered"
}

do_sync_argocd() {
    section_header "Syncing (Argo CD)"
    require_argocd

    if [[ -n "$APP_NAME" ]]; then
        log "Syncing application ${CYAN}${APP_NAME}${RESET}"
        verbose "argocd app sync ${APP_NAME}"
        argocd app sync "$APP_NAME"
    else
        log "Syncing all applications"
        local apps
        apps=$(kubectl_cmd get applications -n "$NAMESPACE" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
        if [[ -z "$apps" ]]; then
            warn "No applications found to sync"
            return
        fi
        while IFS= read -r app; do
            [[ -z "$app" ]] && continue
            log "Syncing ${app}"
            argocd app sync "$app" || warn "Failed to sync ${app}"
        done <<< "$apps"
    fi

    log "Sync triggered"
}

do_sync() {
    case "$GITOPS_TOOL" in
        flux)    do_sync_flux ;;
        argocd)  do_sync_argocd ;;
        *)       die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
    esac
}

# ─────────────────────────────────────────────────────────────────────
# Validate (pre-flight)
# ─────────────────────────────────────────────────────────────────────

do_validate() {
    section_header "Pre-flight Validation"
    require_kubectl

    local checks_passed=0
    local checks_failed=0

    # Check kubectl connectivity
    log "Checking kubectl connectivity"
    if kubectl_cmd cluster-info >/dev/null 2>&1; then
        field_color "Cluster access" "${GREEN}" "OK"
        checks_passed=$((checks_passed + 1))
    else
        field_color "Cluster access" "${RED}" "FAILED"
        checks_failed=$((checks_failed + 1))
    fi

    # Check namespace exists
    log "Checking namespace ${CYAN}${NAMESPACE}${RESET}"
    if kubectl_cmd get namespace "$NAMESPACE" >/dev/null 2>&1; then
        field_color "Namespace" "${GREEN}" "${NAMESPACE} exists"
        checks_passed=$((checks_passed + 1))
    else
        field_color "Namespace" "${YELLOW}" "${NAMESPACE} does not exist (will be created)"
        checks_passed=$((checks_passed + 1))
    fi

    # Check CRDs installed
    log "Checking CRDs"
    if [[ "$GITOPS_TOOL" == "flux" ]]; then
        if kubectl_cmd get crd gitrepositories.source.toolkit.fluxcd.io >/dev/null 2>&1; then
            field_color "Flux CRDs" "${GREEN}" "installed"
            checks_passed=$((checks_passed + 1))
        else
            field_color "Flux CRDs" "${YELLOW}" "not installed"
            checks_passed=$((checks_passed + 1))
        fi
    elif [[ "$GITOPS_TOOL" == "argocd" ]]; then
        if kubectl_cmd get crd applications.argoproj.io >/dev/null 2>&1; then
            field_color "ArgoCD CRDs" "${GREEN}" "installed"
            checks_passed=$((checks_passed + 1))
        else
            field_color "ArgoCD CRDs" "${YELLOW}" "not installed"
            checks_passed=$((checks_passed + 1))
        fi
    fi

    # Check git repo accessible
    if [[ -n "$GIT_REPO" ]]; then
        log "Checking git repository accessibility"
        require_git
        if git ls-remote "$GIT_REPO" HEAD >/dev/null 2>&1; then
            field_color "Git repository" "${GREEN}" "accessible"
            checks_passed=$((checks_passed + 1))
        else
            field_color "Git repository" "${RED}" "not accessible"
            checks_failed=$((checks_failed + 1))
        fi
    else
        verbose "No git repository specified, skipping connectivity check"
    fi

    # Check tool CLI available
    log "Checking CLI tools"
    if [[ "$GITOPS_TOOL" == "flux" ]]; then
        if command -v flux >/dev/null 2>&1; then
            local flux_ver
            flux_ver=$(flux version --client 2>/dev/null | head -1 || echo "unknown")
            field_color "flux CLI" "${GREEN}" "$flux_ver"
            checks_passed=$((checks_passed + 1))
        else
            field_color "flux CLI" "${RED}" "not found"
            checks_failed=$((checks_failed + 1))
        fi
    elif [[ "$GITOPS_TOOL" == "argocd" ]]; then
        if command -v argocd >/dev/null 2>&1; then
            local argocd_ver
            argocd_ver=$(argocd version --client --short 2>/dev/null || echo "unknown")
            field_color "argocd CLI" "${GREEN}" "$argocd_ver"
            checks_passed=$((checks_passed + 1))
        else
            field_color "argocd CLI" "${RED}" "not found"
            checks_failed=$((checks_failed + 1))
        fi
    fi

    section_header "Validation Summary"
    field_color "Passed" "${GREEN}" "$checks_passed"
    if [[ "$checks_failed" -gt 0 ]]; then
        field_color "Failed" "${RED}" "$checks_failed"
        die "Validation failed with ${checks_failed} error(s)"
    else
        field_color "Failed" "${GREEN}" "0"
        log "All pre-flight checks passed"
    fi
}

# ─────────────────────────────────────────────────────────────────────
# Teardown
# ─────────────────────────────────────────────────────────────────────

do_teardown_flux() {
    section_header "Tearing Down Flux"
    require_flux

    confirm_action "Remove Flux from the cluster?"

    log "Uninstalling Flux"
    verbose "flux uninstall --namespace=${NAMESPACE} --silent"
    flux uninstall --namespace="$NAMESPACE" --silent

    log "Flux has been removed from the cluster"
}

do_teardown_argocd() {
    section_header "Tearing Down Argo CD"
    require_kubectl

    confirm_action "Remove Argo CD from the cluster (delete namespace ${NAMESPACE})?"

    log "Deleting namespace ${CYAN}${NAMESPACE}${RESET}"
    kubectl_cmd delete namespace "$NAMESPACE" --wait=true

    log "Argo CD has been removed from the cluster"
}

do_teardown() {
    case "$GITOPS_TOOL" in
        flux)    do_teardown_flux ;;
        argocd)  do_teardown_argocd ;;
        *)       die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
    esac
}

# ─────────────────────────────────────────────────────────────────────
# Help
# ─────────────────────────────────────────────────────────────────────

show_help() {
    cat <<EOF
${BOLD}GitOps Bootstrap v${VERSION}${RESET}
Bootstrap GitOps (Flux or Argo CD) on a Kubernetes cluster.

${BOLD}MODES:${RESET}
  --install TOOL         Install GitOps tool on the cluster (flux or argocd)
  --status               Show GitOps deployment status
  --add-source           Add a git repository source
  --sync                 Trigger reconciliation / sync
  --validate             Run pre-flight checks
  --teardown             Remove GitOps from the cluster

${BOLD}OPTIONS:${RESET}
  --tool TOOL            GitOps tool: flux (default) or argocd
  --repo URL             Git repository URL
  --branch BRANCH        Git branch (default: main)
  --path PATH            Path within the repo (default: ./clusters/default)
  --namespace NS         GitOps namespace (default: flux-system or argocd)
  --source-name NAME     Name for the git source (default: main)
  --kustomization NAME   Name for the kustomization (default: default)
  --app NAME             Application name (for argocd sync)
  --kubeconfig FILE      Path to kubeconfig file
  --context CTX          Kubernetes context name
  --yes                  Skip confirmation prompts
  --verbose              Enable verbose output
  --no-color             Disable colored output
  --help                 Show this help message

${BOLD}ENVIRONMENT VARIABLES:${RESET}
  GITOPS_TOOL            GitOps tool (flux or argocd)
  GITOPS_REPO            Git repository URL
  GITOPS_BRANCH          Git branch
  GITOPS_PATH            Path within the repo
  GITOPS_NAMESPACE       GitOps namespace
  KUBECONFIG             Path to kubeconfig file
  KUBE_CONTEXT           Kubernetes context name
  VERBOSE                Enable verbose output (true/false)
  COLOR                  Color mode (auto, always, never)

${BOLD}EXAMPLES:${RESET}
  ${SCRIPT_NAME} --install flux --repo git@github.com:org/infra.git
  ${SCRIPT_NAME} --install argocd
  ${SCRIPT_NAME} --status --tool argocd
  ${SCRIPT_NAME} --add-source --repo git@github.com:org/infra.git
  ${SCRIPT_NAME} --sync --tool flux
  ${SCRIPT_NAME} --validate --repo git@github.com:org/infra.git
  ${SCRIPT_NAME} --teardown --tool flux --yes
EOF
    exit 0
}

# ─────────────────────────────────────────────────────────────────────
# Parse arguments
# ─────────────────────────────────────────────────────────────────────

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --install)
                RUN_MODE="install"
                if [[ "${2:-}" == "flux" || "${2:-}" == "argocd" ]]; then
                    GITOPS_TOOL="$2"; shift
                fi
                shift
                ;;
            --status)           RUN_MODE="status"; shift ;;
            --add-source)       RUN_MODE="add-source"; shift ;;
            --sync)             RUN_MODE="sync"; shift ;;
            --validate)         RUN_MODE="validate"; shift ;;
            --teardown)         RUN_MODE="teardown"; shift ;;
            --tool)             GITOPS_TOOL="${2:-}"; shift 2 || die "--tool requires a value" ;;
            --repo)             GIT_REPO="${2:-}"; shift 2 || die "--repo requires a URL" ;;
            --branch)           GIT_BRANCH="${2:-}"; shift 2 || die "--branch requires a value" ;;
            --path)             GIT_PATH="${2:-}"; shift 2 || die "--path requires a value" ;;
            --namespace)        NAMESPACE="${2:-}"; shift 2 || die "--namespace requires a value" ;;
            --source-name)      SOURCE_NAME="${2:-}"; shift 2 || die "--source-name requires a value" ;;
            --kustomization)    KUSTOMIZATION_NAME="${2:-}"; shift 2 || die "--kustomization requires a value" ;;
            --app)              APP_NAME="${2:-}"; shift 2 || die "--app requires a name" ;;
            --kubeconfig)       KUBECONFIG_FILE="${2:-}"; shift 2 || die "--kubeconfig requires a file" ;;
            --context)          KUBE_CTX="${2:-}"; shift 2 || die "--context requires a name" ;;
            --yes)              CONFIRM_YES=true; shift ;;
            --verbose)          VERBOSE="true"; shift ;;
            --no-color)         COLOR="never"; shift ;;
            --help)             setup_colors; show_help ;;
            *)                  die "Unknown option: $1" ;;
        esac
    done

    if [[ -z "$RUN_MODE" ]]; then err "No mode specified"; echo ""; show_help; exit 1; fi

    case "$GITOPS_TOOL" in
        flux|argocd) ;;
        *) die "Invalid tool '${GITOPS_TOOL}'. Must be 'flux' or 'argocd'." ;;
    esac
}

# ─────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────

main() {
    parse_args "$@"
    setup_colors
    resolve_namespace

    verbose "Script: ${SCRIPT_NAME}"
    verbose "Mode: ${RUN_MODE}"
    verbose "Tool: ${GITOPS_TOOL}"
    verbose "Namespace: ${NAMESPACE}"
    [[ -n "$GIT_REPO" ]] && verbose "Repository: ${GIT_REPO}"
    [[ -n "$KUBECONFIG_FILE" ]] && verbose "Kubeconfig: ${KUBECONFIG_FILE}"
    [[ -n "$KUBE_CTX" ]] && verbose "Context: ${KUBE_CTX}"

    case "$RUN_MODE" in
        install)     do_install ;;
        status)      do_status ;;
        add-source)  do_add_source ;;
        sync)        do_sync ;;
        validate)    do_validate ;;
        teardown)    do_teardown ;;
        *)           die "Unknown mode: ${RUN_MODE}" ;;
    esac

    local elapsed=$(( $(date +%s) - START_TIME ))
    verbose "Completed in ${elapsed}s"
}

main "$@"
