#!/usr/bin/env bash
set -Eeuo pipefail

CONFIG_DIR="${LEGO_CONFIG_DIR:-/etc/lego}"
CONFIG_FILE="${LEGO_CONFIG_FILE:-${CONFIG_DIR}/.lego.yml}"
PRE_DIR="${LEGO_PRE_DIR:-${CONFIG_DIR}/pre}"
POST_DIR="${LEGO_POST_DIR:-${CONFIG_DIR}/post}"
LOCAL_HOOK_DIR="${LEGO_LOCAL_HOOK_DIR:-${CONFIG_DIR}/hooks}"
MASTER_PRE_HOOK="${LEGO_MASTER_PRE_HOOK:-/usr/libexec/lego/pre-hook}"
MASTER_POST_HOOK="${LEGO_MASTER_POST_HOOK:-/usr/libexec/lego/post-hook}"
DEFAULT_INTERNAL_ACME_URL="${LEGO_INTERNAL_ACME_URL:-https://advbufdc2.advance2000.com/acme}"

log() { printf '%s\n' "$*"; }
die() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

require_root() {
    [[ ${EUID} -eq 0 ]] || die "lego-init must be run as root"
}

trim() {
    local value="$1"
    value="${value#"${value%%[![:space:]]*}"}"
    value="${value%"${value##*[![:space:]]}"}"
    printf '%s' "${value}"
}

normalize_service() {
    local service
    service="$(trim "$1")"
    [[ -n "${service}" ]] || return 0

    case "${service,,}" in
        none|no|n)
            return 0
            ;;
    esac

    [[ "${service}" == *.service ]] || service="${service}.service"
    [[ "${service}" =~ ^[A-Za-z0-9_.@:-]+\.service$ ]] ||
        die "Invalid systemd service name: ${service}"

    printf '%s\n' "${service}"
}

# Use grep ERE to avoid Bash parser ambiguity around the end anchor.
is_valid_fqdn() {
    local domain="$1"
    [[ ${#domain} -le 253 ]] || return 1
    [[ "${domain}" != *..* ]] || return 1
    grep -Eq '^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$' <<<"${domain}"
}

yaml_quote() {
    local value="$1"
    value="${value//\'/\'\'}"
    printf "'%s'" "${value}"
}

write_service_hooks() {
    local service="$1"
    local hook_name="systemd-${service%.service}"
    local stop_script="${LOCAL_HOOK_DIR}/stop-${hook_name}"
    local start_script="${LOCAL_HOOK_DIR}/start-${hook_name}"

    install -d -m 0755 "${PRE_DIR}" "${POST_DIR}" "${LOCAL_HOOK_DIR}"

    cat >"${stop_script}" <<EOF_STOP
#!/usr/bin/env bash
set -Eeuo pipefail
systemctl stop $(printf '%q' "${service}")
EOF_STOP

    cat >"${start_script}" <<EOF_START
#!/usr/bin/env bash
set -Eeuo pipefail
systemctl start $(printf '%q' "${service}")
EOF_START

    chmod 0750 "${stop_script}" "${start_script}"
    ln -sfn "${stop_script}" "${PRE_DIR}/20-${hook_name}"
    ln -sfn "${start_script}" "${POST_DIR}/20-${hook_name}"
}

main() {
    require_root

    local domain_input ca_choice internal=false acme_url="" service=""
    local pfx_choice pfx_enabled=false pfx_password=""
    local -a fqdn_domains=() domains=()
    local -A seen=()

    read -r -p "Domains (space or comma separated FQDNs): " domain_input
    domain_input="${domain_input//,/ }"

    local item domain short
    for item in ${domain_input}; do
        domain="${item,,}"
        domain="${domain%.}"
        is_valid_fqdn "${domain}" || die "Invalid FQDN: ${item}"

        if [[ -z "${seen[${domain}]:-}" ]]; then
            fqdn_domains+=("${domain}")
            seen["${domain}"]=1
        fi
    done

    ((${#fqdn_domains[@]} > 0)) || die "At least one FQDN is required"

    printf '\nCertificate authority:\n'
    printf '  1) Public Let\x27s Encrypt\n'
    printf '  2) Internal ACME server\n'
    read -r -p "Selection [1]: " ca_choice
    ca_choice="${ca_choice:-1}"

    case "${ca_choice,,}" in
        1|public|external|letsencrypt|letsencrypt-production)
            internal=false
            ;;
        2|internal)
            internal=true
            read -r -p "Internal ACME directory URL [${DEFAULT_INTERNAL_ACME_URL}]: " acme_url
            acme_url="${acme_url:-${DEFAULT_INTERNAL_ACME_URL}}"
            [[ "${acme_url}" =~ ^https?://[^[:space:]]+$ ]] || die "Invalid ACME URL"
            ;;
        *)
            die "Unknown certificate authority selection: ${ca_choice}"
            ;;
    esac

    read -r -p "Generate a PKCS#12/PFX file? [y/N]: " pfx_choice
    case "${pfx_choice,,}" in
        y|yes)
            pfx_enabled=true
            read -r -s -p "PFX password (leave blank for no password): " pfx_password
            printf '\n'
            ;;
        ""|n|no)
            pfx_enabled=false
            ;;
        *)
            die "Invalid PFX selection: ${pfx_choice}"
            ;;
    esac

    read -r -p "Service to stop/start for TLS-ALPN-01 [none]: " service
    service="$(normalize_service "${service}")"

    for domain in "${fqdn_domains[@]}"; do
        domains+=("${domain}")

        if ${internal}; then
            short="${domain%%.*}"
            if [[ -n "${short}" && -z "${seen[${short}]:-}" ]]; then
                domains+=("${short}")
                seen["${short}"]=1
            fi
        fi
    done

    install -d -m 0700 "${CONFIG_DIR}"
    install -d -m 0755 "${PRE_DIR}" "${POST_DIR}" "${LOCAL_HOOK_DIR}"

    if [[ -n "${service}" ]]; then
        systemctl cat "${service}" >/dev/null 2>&1 ||
            die "Systemd service not found: ${service}"
        write_service_hooks "${service}"
    fi

    local tmp_file cert_id
    cert_id="${fqdn_domains[0]}"
    tmp_file="$(mktemp "${CONFIG_DIR}/.lego.yml.XXXXXX")"
    trap 'rm -f -- "${tmp_file:-}"' EXIT

    {
        printf 'storage: %s\n\n' "$(yaml_quote "${CONFIG_DIR}")"

        if ${internal}; then
            printf 'accounts:\n'
            printf '  default:\n'
            printf '    server: %s\n' "$(yaml_quote "${acme_url}")"
            printf '    acceptsTermsOfService: true\n\n'
        else
            printf 'accounts:\n'
            printf '  default:\n'
            printf '    server: letsencrypt\n'
            printf '    acceptsTermsOfService: true\n\n'
        fi

        printf 'certificates:\n'
        printf '  %s:\n' "$(yaml_quote "${cert_id}")"
        printf '    account: default\n'
        printf '    challenge: tls-alpn-01\n'
        printf '    keyType: RSA2048\n'
        printf '    noBundle: true\n'
        if ${pfx_enabled}; then
            printf '    pfx:\n'
            printf '      password: %s\n' "$(yaml_quote "${pfx_password}")"
            printf '      format: PBMAC1\n'
        fi
        printf '    renew:\n'
        printf '      days: 1\n'
        printf '      disableRandomSleep: true\n'
        printf '    domains:\n'
        for domain in "${domains[@]}"; do
            printf '      - %s\n' "$(yaml_quote "${domain}")"
        done

        printf '\nhooks:\n'
        printf '  pre:\n'
        printf '    command: %s\n' "$(yaml_quote "${MASTER_PRE_HOOK}")"
        printf '    timeout: 5m\n'
        printf '  post:\n'
        printf '    command: %s\n' "$(yaml_quote "${MASTER_POST_HOOK}")"
        printf '    timeout: 5m\n'
    } >"${tmp_file}"

    chmod 0600 "${tmp_file}"
    mv -f -- "${tmp_file}" "${CONFIG_FILE}"
    trap - EXIT

    log ""
    log "Created ${CONFIG_FILE}"
    log "Challenge: TLS-ALPN-01"
    log "Certificate name: ${cert_id}"
    if ${pfx_enabled}; then
        if [[ -n "${pfx_password}" ]]; then
            log "PFX output: enabled (password protected)"
        else
            log "PFX output: enabled (empty password)"
        fi
    else
        log "PFX output: disabled"
    fi
    log "Domains:"
    printf '  %s\n' "${domains[@]}"

    if [[ -n "${service}" ]]; then
        log "Service hooks: stop/start ${service}"
    else
        log "Service hooks: none"
    fi

    log ""
    if command -v systemctl >/dev/null 2>&1; then
        systemctl daemon-reload
        systemctl enable --now lego-update.timer
        log "Renewal timer: enabled (daily at 01:30 local time)"
    else
        log "Renewal timer: not enabled because systemctl is unavailable"
    fi

    log ""
    log "Request or renew the certificate manually with:"
    log "  lego-update"
    log ""
    log "View the renewal schedule with:"
    log "  systemctl list-timers lego-update.timer"
}

main "$@"
