#!/bin/bash

################################################
#### SSL Certificate Deployer               ####
#### Deploy certs to multiple services      ####
####                                        ####
#### Author: Phil Connor                    ####
#### License: MIT                            ####
#### Contact: contact@mylinux.work           ####
#### Version: 1.00-030326                   ####
################################################

set -o pipefail

SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_NAME

# Runtime variables
CERT_FILE=""
KEY_FILE=""
CA_FILE=""
TARGETS=""
DRY_RUN=false
BACKUP=false
DEBUG=${DEBUG:-}

handle_error() {
    local exit_code=$1
    local line_number=$2
    echo "Error: $SCRIPT_NAME failed at line $line_number with exit code $exit_code" >&2
    exit "$exit_code"
}

trap 'handle_error $? $LINENO' ERR

debug_echo() {
    if [[ -n "$DEBUG" ]]; then
        echo "[DEBUG] $*" >&2
    fi
}

info() {
    echo "[INFO] $*"
}

warn() {
    echo "[WARN] $*" >&2
}

error() {
    echo "[ERROR] $*" >&2
}

show_help() {
    cat << EOF
Usage: $SCRIPT_NAME [OPTIONS]

Deploy SSL certificates to multiple service targets in a single run.

OPTIONS:
    --cert FILE       Path to the SSL certificate file (required)
    --key FILE        Path to the SSL private key file (required)
    --ca FILE         Path to the CA bundle file (optional)
    --targets LIST    Comma-separated list of targets (required)
    --dry-run         Show what would be done without making changes
    --backup          Backup existing certificates before overwriting
    --help, -h        Show this help message

SUPPORTED TARGETS:
    nginx          Copy cert+key to /etc/nginx/ssl/, reload nginx
    apache         Copy cert+key to /etc/httpd/ssl/ or /etc/apache2/ssl/, reload
    postfix        Update TLS cert/key in main.cf, reload postfix
    dovecot        Update ssl_cert/ssl_key in dovecot config, reload dovecot
    artifactory    Import cert into Artifactory Java keystore, restart
    bitbucket      Import cert into Bitbucket Java keystore, restart
    jira           Import cert into Jira Java keystore, restart
    haproxy        Concatenate cert+key into PEM at /etc/haproxy/certs/, reload
    system         Update system CA trust store

ENVIRONMENT VARIABLES:
    DEBUG           Enable debug output when set

EXAMPLES:
    $SCRIPT_NAME --cert server.crt --key server.key --targets nginx,haproxy
    $SCRIPT_NAME --cert server.crt --key server.key --ca ca-bundle.crt --targets apache,postfix,dovecot
    $SCRIPT_NAME --cert server.crt --key server.key --targets artifactory,bitbucket,jira --backup
    $SCRIPT_NAME --cert server.crt --key server.key --targets system --dry-run
    DEBUG=1 $SCRIPT_NAME --cert server.crt --key server.key --targets nginx
EOF
}

validate_cert_key_match() {
    local cert="$1"
    local key="$2"

    local cert_modulus
    cert_modulus=$(openssl x509 -noout -modulus -in "$cert" 2>/dev/null | openssl md5)
    local key_modulus
    key_modulus=$(openssl rsa -noout -modulus -in "$key" 2>/dev/null | openssl md5)

    if [[ "$cert_modulus" != "$key_modulus" ]]; then
        error "Certificate and key do not match (modulus mismatch)"
        debug_echo "Cert modulus: $cert_modulus"
        debug_echo "Key modulus:  $key_modulus"
        return 1
    fi

    debug_echo "Certificate and key match"
    return 0
}

backup_file() {
    local file="$1"
    if [[ -f "$file" ]]; then
        local backup_name
        backup_name="${file}.bak.$(date +%Y%m%d%H%M%S)"
        if [[ "$DRY_RUN" == true ]]; then
            info "[DRY RUN] Would backup $file -> $backup_name"
        else
            cp -a "$file" "$backup_name"
            info "Backed up $file -> $backup_name"
        fi
    fi
}

copy_file() {
    local src="$1"
    local dest="$2"

    if [[ "$BACKUP" == true ]]; then
        backup_file "$dest"
    fi

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would copy $src -> $dest"
    else
        cp -a "$src" "$dest"
        chmod 600 "$dest"
        info "Copied $src -> $dest"
    fi
}

reload_service() {
    local service="$1"

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would reload $service"
    else
        if systemctl is-active --quiet "$service" 2>/dev/null; then
            systemctl reload "$service"
            info "Reloaded $service"
        else
            warn "Service $service is not active, skipping reload"
        fi
    fi
}

restart_service() {
    local service="$1"

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would restart $service"
    else
        systemctl restart "$service"
        info "Restarted $service"
    fi
}

get_keystore_password() {
    local password_url="$1"
    local storepass=""

    # Try Vault HTTP API first if URL provided
    if [[ -n "$password_url" ]]; then
        debug_echo "Retrieving keystore password from $password_url"
        storepass=$(curl -sf -X GET "$password_url" 2>/dev/null | jq -r '.data.password // empty' 2>/dev/null || true)
    fi

    # Fall back to Vault CLI
    if [[ -z "$storepass" ]]; then
        debug_echo "Falling back to Vault CLI for keystore password"
        storepass=$(vault kv get -field=password secret/keystore 2>/dev/null || true)
    fi

    # Fall back to default
    if [[ -z "$storepass" ]]; then
        debug_echo "Using default keystore password"
        storepass="changeit"
    fi

    echo "$storepass"
}

find_java_keystore() {
    local -n java_bin_ref=$1
    local -n keystore_ref=$2

    # Common Java installation paths
    local java_paths=(
        "/opt/jfrog/artifactory/app/third-party/java"
        "/mnt/ebs/bitbucket/*/jre"
        "/mnt/ebs/jira/jre"
        "/usr/lib/jvm/java-*-openjdk"
        "/usr/lib/jvm/default-java"
        "/opt/java"
        "/usr/java/latest"
    )

    # Check JAVA_HOME first
    if [[ -n "${JAVA_HOME:-}" && -x "$JAVA_HOME/bin/keytool" ]]; then
        java_bin_ref="$JAVA_HOME/bin"
        keystore_ref="$JAVA_HOME/lib/security/cacerts"
        if [[ -f "$keystore_ref" ]]; then
            debug_echo "Found Java via JAVA_HOME: $java_bin_ref"
            return 0
        fi
    fi

    # Search common paths with glob expansion
    for path_pattern in "${java_paths[@]}"; do
        for java_dir in $path_pattern; do
            if [[ -d "$java_dir" ]]; then
                local bin_dir="$java_dir/bin"
                local cacerts="$java_dir/lib/security/cacerts"

                if [[ -x "$bin_dir/keytool" && -f "$cacerts" ]]; then
                    java_bin_ref="$bin_dir"
                    keystore_ref="$cacerts"
                    debug_echo "Found Java at: $java_dir"
                    return 0
                fi
            fi
        done
    done

    # Fallback: try system keytool
    if command -v keytool >/dev/null 2>&1; then
        java_bin_ref="$(dirname "$(command -v keytool)")"
        # Try common system keystore locations
        local system_keystores=(
            "/etc/ssl/certs/java/cacerts"
            "/usr/lib/jvm/default-java/lib/security/cacerts"
            "/etc/pki/ca-trust/extracted/java/cacerts"
        )
        for ks in "${system_keystores[@]}"; do
            if [[ -f "$ks" ]]; then
                keystore_ref="$ks"
                debug_echo "Found system Java at: $java_bin_ref"
                return 0
            fi
        done
    fi

    return 1
}

deploy_java_keystore() {
    local keystore="$1"
    local java_bin="$2"
    local alias_name="$3"
    local vault_url="$4"
    local service_name="$5"

    local storepass
    storepass=$(get_keystore_password "$vault_url")

    if [[ "$BACKUP" == true ]]; then
        backup_file "$keystore"
    fi

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would delete alias '$alias_name' from keystore $keystore"
        info "[DRY RUN] Would import $CERT_FILE into keystore $keystore"
        info "[DRY RUN] Would restart $service_name"
    else
        "$java_bin/keytool" -delete -alias "$alias_name" -keystore "$keystore" -storepass "$storepass" 2>/dev/null || true
        "$java_bin/keytool" -import -noprompt -alias "$alias_name" -keystore "$keystore" -file "$CERT_FILE" -storepass "$storepass"
        info "Imported certificate into $keystore"
        restart_service "$service_name"
    fi
}

# ---- Target handlers ----

deploy_nginx() {
    info "Deploying to nginx..."
    local ssl_dir="/etc/nginx/ssl"

    if [[ "$DRY_RUN" != true ]]; then
        mkdir -p "$ssl_dir"
    fi

    copy_file "$CERT_FILE" "$ssl_dir/server.crt"
    copy_file "$KEY_FILE" "$ssl_dir/server.key"

    if [[ -n "$CA_FILE" ]]; then
        copy_file "$CA_FILE" "$ssl_dir/ca-bundle.crt"
    fi

    reload_service nginx
}

deploy_apache() {
    info "Deploying to apache..."
    local ssl_dir=""

    if [[ -d "/etc/httpd" ]]; then
        ssl_dir="/etc/httpd/ssl"
    elif [[ -d "/etc/apache2" ]]; then
        ssl_dir="/etc/apache2/ssl"
    else
        error "Could not detect Apache configuration directory"
        return 1
    fi

    if [[ "$DRY_RUN" != true ]]; then
        mkdir -p "$ssl_dir"
    fi

    copy_file "$CERT_FILE" "$ssl_dir/server.crt"
    copy_file "$KEY_FILE" "$ssl_dir/server.key"

    if [[ -n "$CA_FILE" ]]; then
        copy_file "$CA_FILE" "$ssl_dir/ca-bundle.crt"
    fi

    # Detect and reload the correct service
    if systemctl list-units --type=service --all 2>/dev/null | grep -q "httpd.service"; then
        reload_service httpd
    elif systemctl list-units --type=service --all 2>/dev/null | grep -q "apache2.service"; then
        reload_service apache2
    else
        warn "Could not detect Apache service name"
    fi
}

deploy_postfix() {
    info "Deploying to postfix..."
    local main_cf="/etc/postfix/main.cf"

    if [[ ! -f "$main_cf" ]]; then
        error "Postfix main.cf not found at $main_cf"
        return 1
    fi

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would update smtpd_tls_cert_file in $main_cf to $CERT_FILE"
        info "[DRY RUN] Would update smtpd_tls_key_file in $main_cf to $KEY_FILE"
        info "[DRY RUN] Would reload postfix"
    else
        if [[ "$BACKUP" == true ]]; then
            backup_file "$main_cf"
        fi

        if grep -q "^smtpd_tls_cert_file" "$main_cf"; then
            sed -i "s|^smtpd_tls_cert_file.*|smtpd_tls_cert_file = $CERT_FILE|" "$main_cf"
        else
            echo "smtpd_tls_cert_file = $CERT_FILE" >> "$main_cf"
        fi

        if grep -q "^smtpd_tls_key_file" "$main_cf"; then
            sed -i "s|^smtpd_tls_key_file.*|smtpd_tls_key_file = $KEY_FILE|" "$main_cf"
        else
            echo "smtpd_tls_key_file = $KEY_FILE" >> "$main_cf"
        fi

        info "Updated $main_cf with certificate paths"
        reload_service postfix
    fi
}

deploy_dovecot() {
    info "Deploying to dovecot..."
    local dovecot_conf=""

    if [[ -f "/etc/dovecot/conf.d/10-ssl.conf" ]]; then
        dovecot_conf="/etc/dovecot/conf.d/10-ssl.conf"
    elif [[ -f "/etc/dovecot/dovecot.conf" ]]; then
        dovecot_conf="/etc/dovecot/dovecot.conf"
    else
        error "Could not find dovecot configuration"
        return 1
    fi

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would update ssl_cert in $dovecot_conf to <$CERT_FILE"
        info "[DRY RUN] Would update ssl_key in $dovecot_conf to <$KEY_FILE"
        info "[DRY RUN] Would reload dovecot"
    else
        if [[ "$BACKUP" == true ]]; then
            backup_file "$dovecot_conf"
        fi

        if grep -q "^ssl_cert" "$dovecot_conf"; then
            sed -i "s|^ssl_cert.*|ssl_cert = <$CERT_FILE|" "$dovecot_conf"
        else
            echo "ssl_cert = <$CERT_FILE" >> "$dovecot_conf"
        fi

        if grep -q "^ssl_key" "$dovecot_conf"; then
            sed -i "s|^ssl_key.*|ssl_key = <$KEY_FILE|" "$dovecot_conf"
        else
            echo "ssl_key = <$KEY_FILE" >> "$dovecot_conf"
        fi

        info "Updated $dovecot_conf with certificate paths"
        reload_service dovecot
    fi
}

deploy_artifactory() {
    info "Deploying to artifactory..."
    local java_bin="/opt/jfrog/artifactory/app/third-party/java/bin"
    local keystore="/opt/jfrog/artifactory/app/third-party/java/lib/security/cacerts"

    if [[ ! -x "$java_bin/keytool" || ! -f "$keystore" ]]; then
        debug_echo "Artifactory default paths not found, searching for Java"
        if ! find_java_keystore java_bin keystore; then
            error "Could not find Java keytool or keystore for Artifactory"
            return 1
        fi
    fi

    deploy_java_keystore "$keystore" "$java_bin" "ssl-cert" "" "artifactory"
}

deploy_bitbucket() {
    info "Deploying to bitbucket..."
    local java_bin=""
    local keystore=""

    # Check app-specific paths first with glob
    for bb_dir in /mnt/ebs/bitbucket/*/jre; do
        if [[ -d "$bb_dir" && -x "$bb_dir/bin/keytool" && -f "$bb_dir/lib/security/cacerts" ]]; then
            java_bin="$bb_dir/bin"
            keystore="$bb_dir/lib/security/cacerts"
            break
        fi
    done

    if [[ -z "$java_bin" || -z "$keystore" ]]; then
        debug_echo "Bitbucket default paths not found, searching for Java"
        if ! find_java_keystore java_bin keystore; then
            error "Could not find Java keytool or keystore for Bitbucket"
            return 1
        fi
    fi

    deploy_java_keystore "$keystore" "$java_bin" "ssl-cert" "" "atlbitbucket"
}

deploy_jira() {
    info "Deploying to jira..."
    local java_bin="/mnt/ebs/jira/jre/bin"
    local keystore="/mnt/ebs/jira/jre/lib/security/cacerts"

    if [[ ! -x "$java_bin/keytool" || ! -f "$keystore" ]]; then
        debug_echo "Jira default paths not found, searching for Java"
        if ! find_java_keystore java_bin keystore; then
            error "Could not find Java keytool or keystore for Jira"
            return 1
        fi
    fi

    deploy_java_keystore "$keystore" "$java_bin" "ssl-cert" "" "jira"
}

deploy_haproxy() {
    info "Deploying to haproxy..."
    local cert_dir="/etc/haproxy/certs"
    local pem_file="$cert_dir/server.pem"

    if [[ "$DRY_RUN" != true ]]; then
        mkdir -p "$cert_dir"
    fi

    if [[ "$BACKUP" == true ]]; then
        backup_file "$pem_file"
    fi

    if [[ "$DRY_RUN" == true ]]; then
        info "[DRY RUN] Would concatenate $CERT_FILE + $KEY_FILE -> $pem_file"
        info "[DRY RUN] Would reload haproxy"
    else
        cat "$CERT_FILE" "$KEY_FILE" > "$pem_file"
        chmod 600 "$pem_file"
        info "Created combined PEM at $pem_file"
        reload_service haproxy
    fi
}

deploy_system() {
    info "Deploying to system CA trust store..."

    if [[ -z "$CA_FILE" && -z "$CERT_FILE" ]]; then
        error "No certificate or CA bundle provided for system trust store"
        return 1
    fi

    local cert_to_install="${CA_FILE:-$CERT_FILE}"

    if command -v update-ca-trust >/dev/null 2>&1; then
        # RHEL/CentOS/Fedora/Rocky/Alma
        local trust_dir="/etc/pki/ca-trust/source/anchors"
        local cert_name
        cert_name=$(basename "$cert_to_install")

        if [[ "$DRY_RUN" == true ]]; then
            info "[DRY RUN] Would copy $cert_to_install -> $trust_dir/$cert_name"
            info "[DRY RUN] Would run update-ca-trust"
        else
            copy_file "$cert_to_install" "$trust_dir/$cert_name"
            update-ca-trust
            info "Updated system CA trust store (RHEL-based)"
        fi
    elif command -v update-ca-certificates >/dev/null 2>&1; then
        # Debian/Ubuntu
        local trust_dir="/usr/local/share/ca-certificates"
        local cert_name
        cert_name=$(basename "$cert_to_install")
        # Debian requires .crt extension
        cert_name="${cert_name%.*}.crt"

        if [[ "$DRY_RUN" == true ]]; then
            info "[DRY RUN] Would copy $cert_to_install -> $trust_dir/$cert_name"
            info "[DRY RUN] Would run update-ca-certificates"
        else
            copy_file "$cert_to_install" "$trust_dir/$cert_name"
            update-ca-certificates
            info "Updated system CA trust store (Debian-based)"
        fi
    else
        error "Could not find update-ca-trust or update-ca-certificates"
        return 1
    fi
}

parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --cert)
                CERT_FILE="$2"
                shift 2
                ;;
            --key)
                KEY_FILE="$2"
                shift 2
                ;;
            --ca)
                CA_FILE="$2"
                shift 2
                ;;
            --targets)
                TARGETS="$2"
                shift 2
                ;;
            --dry-run)
                DRY_RUN=true
                shift
                ;;
            --backup)
                BACKUP=true
                shift
                ;;
            --help|-h)
                show_help
                exit 0
                ;;
            *)
                error "Unknown option: $1"
                show_help >&2
                exit 1
                ;;
        esac
    done
}

validate_inputs() {
    if [[ -z "$CERT_FILE" ]]; then
        error "Certificate file is required (--cert)"
        exit 1
    fi

    if [[ -z "$KEY_FILE" ]]; then
        error "Key file is required (--key)"
        exit 1
    fi

    if [[ -z "$TARGETS" ]]; then
        error "At least one target is required (--targets)"
        exit 1
    fi

    if [[ ! -f "$CERT_FILE" ]]; then
        error "Certificate file not found: $CERT_FILE"
        exit 1
    fi

    if [[ ! -f "$KEY_FILE" ]]; then
        error "Key file not found: $KEY_FILE"
        exit 1
    fi

    if [[ -n "$CA_FILE" && ! -f "$CA_FILE" ]]; then
        error "CA bundle file not found: $CA_FILE"
        exit 1
    fi

    if ! openssl x509 -noout -text -in "$CERT_FILE" >/dev/null 2>&1; then
        error "Invalid certificate file: $CERT_FILE"
        exit 1
    fi

    if ! openssl rsa -noout -check -in "$KEY_FILE" >/dev/null 2>&1; then
        error "Invalid key file: $KEY_FILE"
        exit 1
    fi

    if ! validate_cert_key_match "$CERT_FILE" "$KEY_FILE"; then
        exit 1
    fi
}

deploy_target() {
    local target="$1"

    case "$target" in
        nginx)       deploy_nginx ;;
        apache)      deploy_apache ;;
        postfix)     deploy_postfix ;;
        dovecot)     deploy_dovecot ;;
        artifactory) deploy_artifactory ;;
        bitbucket)   deploy_bitbucket ;;
        jira)        deploy_jira ;;
        haproxy)     deploy_haproxy ;;
        system)      deploy_system ;;
        *)
            error "Unknown target: $target"
            error "Valid targets: nginx, apache, postfix, dovecot, artifactory, bitbucket, jira, haproxy, system"
            return 1
            ;;
    esac
}

main() {
    parse_arguments "$@"
    validate_inputs

    if [[ "$DRY_RUN" == true ]]; then
        info "Running in DRY RUN mode — no changes will be made"
    fi

    local failed=0
    local succeeded=0

    IFS=',' read -ra target_list <<< "$TARGETS"
    for target in "${target_list[@]}"; do
        # Trim whitespace
        target=$(echo "$target" | tr -d '[:space:]')
        info "--- Deploying to target: $target ---"

        if deploy_target "$target"; then
            ((succeeded++))
            info "Target $target: OK"
        else
            ((failed++))
            error "Target $target: FAILED"
        fi
        echo
    done

    info "Deployment complete: $succeeded succeeded, $failed failed"

    if [[ $failed -gt 0 ]]; then
        return 1
    fi
}

# Execute main function if script is run directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi
