#!/bin/bash

# Toggle the crowdsec-host-logs marker block in libreportal's live compose
# ("on" uncomments, "off" re-comments) and recreate the container so the
# mount set takes effect.
crowdsecToggleLibrePortalLogMounts() {
    local mode="$1"
    local compose="${containers_dir}libreportal/docker-compose.yml"
    [[ -f "$compose" ]] || return 0

    case "$mode" in
        on)
            sudo sed -i '/# >>> crowdsec-host-logs >>>/,/# <<< crowdsec-host-logs <<</ {
                /crowdsec.*\.log:/ s/^\([[:space:]]*\)#-/\1-/
            }' "$compose"
            ;;
        off)
            sudo sed -i '/# >>> crowdsec-host-logs >>>/,/# <<< crowdsec-host-logs <<</ {
                /crowdsec.*\.log:/ s/^\([[:space:]]*\)-/\1#-/
            }' "$compose"
            ;;
        *) isError "crowdsecToggleLibrePortalLogMounts: bad mode '$mode'"; return 1 ;;
    esac

    if runFileOp docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^libreportal-service$'; then
        isNotice "Recreating libreportal so log mount toggle takes effect..."
        ( cd "${containers_dir}libreportal" && runAsManager docker compose up -d >/dev/null 2>&1 ) || true
    fi
}

installCrowdsecHost()
{
    # Touch + chmod 0644 the host log files so the libreportal container
    # (UID 1001) can read them. Then enable the bind-mounts.
    for _l in /var/log/crowdsec.log /var/log/crowdsec-firewall-bouncer.log; do
        sudo touch "$_l" 2>/dev/null || true
        sudo chmod 0644 "$_l" 2>/dev/null || true
    done
    crowdsecToggleLibrePortalLogMounts on

    local desired_state="${CFG_CROWDSEC_ENABLED:-true}"
    local is_installed="false"
    command -v cscli >/dev/null 2>&1 && is_installed="true"

    if [[ "$desired_state" == "true" && "$is_installed" == "false" ]]; then
        isHeader "Install CrowdSec"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Adding the CrowdSec apt repository."
        echo ""

        local result=$(curl -fsSL https://install.crowdsec.net | sudo bash)
        checkSuccess "Adding CrowdSec repository"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Installing the CrowdSec agent."
        echo ""
        isNotice "First-time install ~30-70 MB GeoLite2 DB + parser hub, 1-3 mins."

        local result=$(sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q crowdsec </dev/null 2>&1)
        checkSuccess "Installing CrowdSec package"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Installing the CrowdSec firewall bouncer."
        echo ""

        local result=$(sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q crowdsec-firewall-bouncer-nftables </dev/null 2>&1)
        checkSuccess "Installing CrowdSec firewall bouncer (nftables)"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Enabling CrowdSec services."
        echo ""

        local result=$(runSystem systemctl enable --now crowdsec)
        checkSuccess "Enabling CrowdSec agent"

        local result=$(runSystem systemctl enable --now crowdsec-firewall-bouncer)
        checkSuccess "Enabling CrowdSec firewall bouncer"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Installing baseline collections."
        echo ""

        local result=$(runSystem cscli collections install crowdsecurity/linux)
        checkSuccess "Installing crowdsecurity/linux collection"

        local result=$(runSystem cscli collections install crowdsecurity/sshd)
        checkSuccess "Installing crowdsecurity/sshd collection"

        local result=$(runSystem systemctl reload crowdsec)
        checkSuccess "Reloading CrowdSec to pick up collections"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Community blocklist (CAPI) toggle."
        echo ""

        # CAPI registration is what subscribes to the community blocklist
        # AND sends anonymous attack signals back. apt postinst registers
        # by default; honour CFG_CROWDSEC_COMMUNITY_BLOCKLIST=false by
        # unregistering. Idempotent on either branch.
        local community_blocklist="${CFG_CROWDSEC_COMMUNITY_BLOCKLIST:-true}"
        if [[ "$community_blocklist" == "true" ]]; then
            if runSystem cscli capi status 2>&1 | grep -qi 'You can successfully'; then
                isNotice "Community blocklist already registered."
            else
                local result=$(runSystem cscli capi register 2>&1)
                checkSuccess "Registered with CrowdSec Central API (community blocklist)"
            fi
        else
            local result=$(runSystem cscli capi unregister 2>&1)
            checkSuccess "Unregistered from CrowdSec Central API (community blocklist disabled)"
        fi

        ((menu_number++))
        echo ""
        echo "---- $menu_number. SaaS Console enrollment toggle."
        echo ""

        # cscli console enroll <token> registers this agent with the hosted
        # dashboard at app.crowdsec.net. Idempotent: if already enrolled,
        # skip. If toggled off, disenroll. Quietly skipped when the flag
        # is on but the token field is empty (user hasn't pasted one yet).
        local console_enroll="${CFG_CROWDSEC_CONSOLE_ENROLL:-false}"
        local console_token="${CFG_CROWDSEC_CONSOLE_TOKEN:-}"
        local enrolled=false
        runSystem cscli console status 2>&1 | grep -qi 'enrolled' && enrolled=true
        if [[ "$console_enroll" == "true" ]]; then
            if [[ -z "$console_token" ]]; then
                isNotice "Console enrollment ON but CFG_CROWDSEC_CONSOLE_TOKEN is empty — paste your token from app.crowdsec.net to complete."
            elif [[ "$enrolled" == true ]]; then
                isNotice "Already enrolled with the SaaS console — skipping."
            else
                local result=$(runSystem cscli console enroll "$console_token" 2>&1)
                checkSuccess "Enrolled with app.crowdsec.net SaaS console"
            fi
        else
            if [[ "$enrolled" == true ]]; then
                local result=$(runSystem cscli console disenroll 2>&1)
                checkSuccess "Disenrolled from app.crowdsec.net SaaS console"
            else
                isNotice "SaaS console enrollment disabled — skipping."
            fi
        fi

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Wiring LAPI for Traefik bouncer access."
        echo ""

        # Bind LAPI to all interfaces so the Traefik container can reach it
        # via host.docker.internal:host-gateway. The bouncer API key is
        # required (HTTP 401 without it), so internet exposure is gated.
        # External access on 8080 should still be blocked at UFW.
        local lapi_cfg="/etc/crowdsec/config.yaml"
        if [[ -f "$lapi_cfg" ]] && ! sudo grep -qE 'listen_uri:[[:space:]]*0\.0\.0\.0:8080' "$lapi_cfg"; then
            sudo sed -i 's|listen_uri:.*|listen_uri: 0.0.0.0:8080|' "$lapi_cfg"
            checkSuccess "LAPI bound to 0.0.0.0:8080"
            runSystem systemctl restart crowdsec
            checkSuccess "CrowdSec restarted"
        else
            isNotice "LAPI already bound to 0.0.0.0:8080 — skipping."
        fi

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Prometheus metrics endpoint."
        echo ""

        # When monitoring is on, bind CrowdSec's Prometheus metrics endpoint to
        # a docker-reachable address (the 127.0.0.1 default can't be scraped
        # from the Prometheus container). The sed is scoped to the prometheus:
        # block. When off, rebind to localhost if a prior run opened it.
        local cs_cfg="/etc/crowdsec/config.yaml"
        local mon_enabled="${CFG_CROWDSEC_MONITORING:-false}"
        local prom_listen="${CFG_CROWDSEC_PROMETHEUS_LISTEN:-0.0.0.0:6060}"
        local prom_addr="${prom_listen%%:*}"
        local prom_port="${prom_listen##*:}"
        if [[ "$mon_enabled" == "true" && -f "$cs_cfg" ]]; then
            if ! sudo grep -qE "listen_addr:[[:space:]]*${prom_addr}" "$cs_cfg"; then
                sudo sed -i "/^prometheus:/,/^[^[:space:]#]/ {
                    s|enabled:.*|enabled: true|
                    s|listen_addr:.*|listen_addr: ${prom_addr}|
                    s|listen_port:.*|listen_port: ${prom_port}|
                }" "$cs_cfg"
                checkSuccess "CrowdSec metrics endpoint bound to ${prom_listen}"
                runSystem systemctl restart crowdsec
                checkSuccess "CrowdSec restarted"
            else
                isNotice "CrowdSec metrics already bound to ${prom_addr} — skipping."
            fi
        elif [[ -f "$cs_cfg" ]] && sudo grep -qE 'listen_addr:[[:space:]]*0\.0\.0\.0' "$cs_cfg"; then
            sudo sed -i "/^prometheus:/,/^[^[:space:]#]/ s|listen_addr:.*|listen_addr: 127.0.0.1|" "$cs_cfg"
            checkSuccess "CrowdSec metrics endpoint rebound to 127.0.0.1 (monitoring off)"
            runSystem systemctl restart crowdsec
            checkSuccess "CrowdSec restarted"
        else
            isNotice "Monitoring off — CrowdSec metrics endpoint left at its default."
        fi

        # Generate a dedicated bouncer key for Traefik (idempotent: skip if
        # already registered). Two sinks for the value:
        #   1) /etc/crowdsec/traefik_bouncer.key — raw key, bind-mounted into
        #      the Traefik container read-only; the plugin reads it via
        #      crowdsecLapiKeyFile. /etc/crowdsec/ is outside the framework's
        #      sourceScanFiles sweep so a bare key file is safe here.
        #   2) /docker/configs/network/network_crowdsec — CFG_CROWDSEC_TRAEFIK_LAPI_KEY
        #      line, sourced by the framework and visible on the config page.
        #      Editing the CFG var manually does not re-register the bouncer
        #      (use the rotate Tools action for that); this is a visibility
        #      surface, not the auth source of truth.
        local key_file="/etc/crowdsec/traefik_bouncer.key"
        local cfg_file="${configs_dir}security/security_crowdsec"

        if ! runSystem cscli bouncers list -o raw 2>/dev/null | grep -q '^traefik-bouncer'; then
            local bouncer_key
            bouncer_key=$(runSystem cscli bouncers add traefik-bouncer -o raw 2>&1 | tail -1)
            if [[ -n "$bouncer_key" && "$bouncer_key" != *"error"* ]]; then
                echo "$bouncer_key" | sudo tee "$key_file" >/dev/null
                sudo chown "$sudo_user_name:$sudo_user_name" "$key_file"
                sudo chmod 0600 "$key_file"
                checkSuccess "Traefik bouncer API key generated"

                # Write the key into the live config file so it's visible /
                # editable via the framework's config page like any other
                # CFG_* setting.
                if [[ -f "$cfg_file" ]]; then
                    sudo sed -i "s|^CFG_CROWDSEC_TRAEFIK_LAPI_KEY=.*|CFG_CROWDSEC_TRAEFIK_LAPI_KEY=${bouncer_key}|" "$cfg_file"
                    checkSuccess "Key mirrored to CFG_CROWDSEC_TRAEFIK_LAPI_KEY"
                else
                    isNotice "Live config not present yet — key applied on next install."
                fi
            else
                isNotice "Failed to generate bouncer key — Traefik integration won't authenticate. Re-run installCrowdsecHost to retry."
            fi
        else
            isNotice "Bouncer 'traefik-bouncer' already registered — leaving existing key file untouched at $key_file."
        fi

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Verifying CrowdSec / host firewall coexistence."
        echo ""

        # The firewall bouncer needs a moment to install its nftables table
        # after enable. Poll up to ~10s before deciding it's missing.
        local _wait=0
        until runSystem nft list tables 2>/dev/null | grep -qiE 'crowdsec' || [[ $_wait -ge 10 ]]; do
            sleep 1; _wait=$((_wait+1))
        done

        if ! runSystem nft list tables 2>/dev/null | grep -qiE 'crowdsec'; then
            isNotice "CrowdSec nftables table not yet present after ${_wait}s. Bouncer may still be starting; re-run the verification Tools action in a minute if rules don't appear."
        else
            local cs_prio ufw_prio
            cs_prio=$(runSystem nft list ruleset 2>/dev/null | awk '/table .* crowdsec/{flag=1} flag && /priority/{match($0,/priority [-0-9]+/); print substr($0,RSTART+9,RLENGTH-9); exit}')
            ufw_prio=$(runSystem nft list ruleset 2>/dev/null | awk '/chain ufw[a-z0-9-]*input/{flag=1} flag && /priority/{match($0,/priority [-0-9]+/); print substr($0,RSTART+9,RLENGTH-9); exit}')

            if [[ -z "$ufw_prio" ]]; then
                isSuccessful "UFW not in nftables — no ordering needed (CrowdSec prio: ${cs_prio:-?})."
            elif [[ -n "$cs_prio" && "$cs_prio" -lt "$ufw_prio" ]]; then
                isSuccessful "Chain priority correct: CrowdSec ($cs_prio) runs before UFW ($ufw_prio) — bans take precedence."
            else
                isNotice "WARNING: CrowdSec priority (${cs_prio:-unknown}) is not lower than UFW ($ufw_prio)."
                isNotice "         Packets accepted by UFW first won't reach CrowdSec drop rules."
                isNotice "         Fix: run the 'crowdsec_fix_priority' Tools action, or manually edit"
                isNotice "         /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml — set"
                isNotice "         nftables.ipv4.priority and nftables.ipv6.priority to -100, then"
                isNotice "         'sudo systemctl restart crowdsec-firewall-bouncer'."
            fi
        fi

        isSuccessful "CrowdSec installed. Console enrollment OFF (no signals sent). Opt in via Tools tab for community blocklists."

        menu_number=0
        cd

    elif [[ "$desired_state" == "true" && "$is_installed" == "true" ]]; then
        # Already installed — make sure services are running. Quiet path (no header)
        # so re-runs of pre-install don't spam the log when state already matches.
        if ! systemctl is-active --quiet crowdsec; then
            isHeader "Enable CrowdSec"

            ((menu_number++))
            echo ""
            echo "---- $menu_number. Re-enabling CrowdSec services."
            echo ""

            local result=$(runSystem systemctl enable --now crowdsec)
            checkSuccess "Enabling CrowdSec agent"

            local result=$(runSystem systemctl enable --now crowdsec-firewall-bouncer)
            checkSuccess "Enabling CrowdSec firewall bouncer"

            isSuccessful "CrowdSec services re-enabled."
            menu_number=0
        fi

    elif [[ "$desired_state" != "true" && "$is_installed" == "true" ]]; then
        # User flipped CFG_CROWDSEC_ENABLED away from "true" — disable, don't
        # uninstall. Package stays so flipping back is fast; explicit uninstall is
        # a separate Tools action.
        isHeader "Disable CrowdSec"

        ((menu_number++))
        echo ""
        echo "---- $menu_number. Stopping and disabling CrowdSec services."
        echo ""

        local result=$(runSystem systemctl disable --now crowdsec-firewall-bouncer 2>&1)
        checkSuccess "Disabling CrowdSec firewall bouncer"

        local result=$(runSystem systemctl disable --now crowdsec 2>&1)
        checkSuccess "Disabling CrowdSec agent"

        isSuccessful "CrowdSec disabled. Package remains installed — set CFG_CROWDSEC_ENABLED=true to re-enable, or uninstall via the Tools tab."
        menu_number=0
    fi
}
