#!/bin/bash

#############################################################################
# Firewall Operations - Platform-specific IP blocking and management
# Provides variables and functions for adding/removing IPs across all firewalls
# Must be sourced AFTER lib/system-detect.sh has set SYS_* variables
#############################################################################

# Source guard
if [ -n "${_FIREWALL_OPERATIONS_LOADED:-}" ]; then
    return 0
fi
readonly _FIREWALL_OPERATIONS_LOADED=1

#############################################################################
# CSF FIREWALL OPERATIONS
#############################################################################

derive_csf_operations() {
    export SYS_CSF_ALLOW="/etc/csf/csf.allow"
    export SYS_CSF_DENY="/etc/csf/csf.deny"
    export SYS_CSF_WHITELIST="/etc/csf/csf.whitelist"
    export SYS_CSF_REGEX="/etc/csf/csf.regex"
    export SYS_CSF_IGNOREAUTO="/etc/csf/csf.ignoreauto"
    export SYS_CSF_IGNORE="/etc/csf/csf.ignore"
    export SYS_CSF_LOG="/var/log/lfd.log"
    export SYS_CSF_QUEUE="/var/spool/csf"

    # CSF command paths
    export SYS_CSF_BIN="/usr/local/csf/bin"
    export SYS_CSF_CMD="/usr/sbin/csf"
    export SYS_CSF_IP_CMD="/usr/local/csf/bin/csftest.pl"

    # CSF IP blocking command format
    export SYS_CSF_BAN_CMD="csf -d"      # csf -d IP
    export SYS_CSF_UNBAN_CMD="csf -ar"   # csf -ar IP
    export SYS_CSF_ALLOW_CMD="csf -a"    # csf -a IP
}

#############################################################################
# FIREWALLD OPERATIONS
#############################################################################

derive_firewalld_operations() {
    export SYS_FIREWALLD_CONFIG="/etc/firewalld"
    export SYS_FIREWALLD_ZONES="/etc/firewalld/zones"
    export SYS_FIREWALLD_IPSETS="/etc/firewalld/ipsets"
    export SYS_FIREWALLD_SERVICES="/etc/firewalld/services"
    export SYS_FIREWALLD_LOG="/var/log/firewalld"
    export SYS_FIREWALLD_DB="/var/lib/firewalld"

    # firewalld command format
    export SYS_FIREWALLD_BAN_CMD="firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"IP\" reject'"
    export SYS_FIREWALLD_UNBAN_CMD="firewall-cmd --permanent --remove-rich-rule='rule family=\"ipv4\" source address=\"IP\" reject'"
    export SYS_FIREWALLD_ALLOW_CMD="firewall-cmd --permanent --add-source=IP/32"
    export SYS_FIREWALLD_RELOAD="firewall-cmd --reload"

    # firewalld ipset for mass blocking
    export SYS_FIREWALLD_IPSET_NAME="blocked_ips"
    export SYS_FIREWALLD_IPSET_FILE="/etc/firewalld/ipsets/$SYS_FIREWALLD_IPSET_NAME.xml"
}

#############################################################################
# IPTABLES OPERATIONS
#############################################################################

derive_iptables_operations() {
    export SYS_IPTABLES_CONFIG="/etc/sysconfig/iptables"
    export SYS_IPTABLES_RULES_DIR="/etc/iptables"
    export SYS_IPTABLES_STATE_DIR="/proc/net"
    export SYS_IPTABLES_LOG="/var/log/messages"

    # iptables command format
    export SYS_IPTABLES_BAN_CMD="iptables -I INPUT -s IP -j DROP"
    export SYS_IPTABLES_UNBAN_CMD="iptables -D INPUT -s IP -j DROP"
    export SYS_IPTABLES_ALLOW_CMD="iptables -I INPUT -s IP -j ACCEPT"
    export SYS_IPTABLES_SAVE="iptables-save > /etc/iptables/rules.v4"

    # iptables ipset for mass blocking
    export SYS_IPTABLES_IPSET_NAME="blocked_ips"
    export SYS_IPTABLES_IPSET_LIST="ipset list $SYS_IPTABLES_IPSET_NAME"
    export SYS_IPTABLES_IPSET_CREATE="ipset create $SYS_IPTABLES_IPSET_NAME hash:ip"
    export SYS_IPTABLES_IPSET_ADD="ipset add $SYS_IPTABLES_IPSET_NAME IP"
    export SYS_IPTABLES_IPSET_DEL="ipset del $SYS_IPTABLES_IPSET_NAME IP"
    export SYS_IPTABLES_IPSET_FLUSH="ipset flush $SYS_IPTABLES_IPSET_NAME"
}

#############################################################################
# UFW (Ubuntu Firewall) OPERATIONS
#############################################################################

derive_ufw_operations() {
    export SYS_UFW_CONFIG="/etc/ufw"
    export SYS_UFW_BEFORE_RULES="/etc/ufw/before.rules"
    export SYS_UFW_AFTER_RULES="/etc/ufw/after.rules"
    export SYS_UFW_RULES_DIR="/etc/ufw/user.d"
    export SYS_UFW_LOG="/var/log/ufw.log"
    export SYS_UFW_DB="/etc/ufw/user_rules"

    # UFW command format
    export SYS_UFW_BAN_CMD="ufw deny from IP"
    export SYS_UFW_UNBAN_CMD="ufw delete deny from IP"
    export SYS_UFW_ALLOW_CMD="ufw allow from IP"
    export SYS_UFW_RELOAD="ufw reload"

    # UFW ipset for mass blocking (using before.rules)
    export SYS_UFW_IPSET_NAME="blocked_ips"
    export SYS_UFW_BEFORE_RULES_CUSTOM="/etc/ufw/before.rules.d/10-blocked-ips"
}

#############################################################################
# IMUNIFY FIREWALL OPERATIONS
#############################################################################

derive_imunify_operations() {
    export SYS_IMUNIFY_CONFIG="/etc/sysconfig/imunify360"
    export SYS_IMUNIFY_CLI="/usr/bin/imunify360-agent"
    export SYS_IMUNIFY_LOG="/var/log/imunify360"
    export SYS_IMUNIFY_LOG_MAIN="/var/log/imunify360/imunify360.log"
    export SYS_IMUNIFY_DB="/var/lib/imunify360"
    export SYS_IMUNIFY_BLOCKLIST="/var/lib/imunify360/blocklist"
    export SYS_IMUNIFY_WHITELIST="/var/lib/imunify360/whitelist"

    # Imunify command format (via CLI)
    export SYS_IMUNIFY_BAN_CMD="imunify360-agent blacklist add --ip IP"
    export SYS_IMUNIFY_UNBAN_CMD="imunify360-agent blacklist remove --ip IP"
    export SYS_IMUNIFY_ALLOW_CMD="imunify360-agent whitelist add --ip IP"
    export SYS_IMUNIFY_LIST_BLOCKED="imunify360-agent blacklist list"
    export SYS_IMUNIFY_LIST_ALLOWED="imunify360-agent whitelist list"
}

#############################################################################
# PLESK FIREWALL OPERATIONS
#############################################################################

derive_plesk_firewall_operations() {
    export SYS_PLESK_FW_CONFIG="/etc/sysconfig/plesk-firewall"
    export SYS_PLESK_FW_RULES="/etc/sysconfig/plesk-firewall.rules"
    export SYS_PLESK_FW_LOG="/var/log/plesk-firewall.log"
    export SYS_PLESK_FW_WHITELIST="/etc/sysconfig/plesk-firewall.whitelist"
    export SYS_PLESK_FW_BLACKLIST="/etc/sysconfig/plesk-firewall.blacklist"

    # Plesk firewall command (via plesk CLI)
    export SYS_PLESK_FW_CMD="/usr/local/psa/bin/firewall"
}

#############################################################################
# GENERIC FIREWALL IP BLOCKING FUNCTIONS
#############################################################################

# Block an IP across the detected firewall
firewall_block_ip() {
    local ip="$1"
    local reason="${2:-Security block}"

    if [ -z "$ip" ]; then
        echo "ERROR: IP address required" >&2
        return 1
    fi

    case "$SYS_FIREWALL" in
        csf)
            csf -d "$ip" 2>/dev/null || {
                echo "ERROR: Failed to block $ip in CSF" >&2
                return 1
            }
            ;;
        firewalld)
            firewall-cmd --permanent --add-rich-rule="rule family=\"ipv4\" source address=\"$ip\" reject" 2>/dev/null || {
                echo "ERROR: Failed to block $ip in firewalld" >&2
                return 1
            }
            firewall-cmd --reload 2>/dev/null
            ;;
        iptables)
            if command -v ipset &>/dev/null; then
                ipset add "$SYS_IPTABLES_IPSET_NAME" "$ip" 2>/dev/null || {
                    # Create set if it doesn't exist
                    ipset create "$SYS_IPTABLES_IPSET_NAME" hash:ip 2>/dev/null
                    ipset add "$SYS_IPTABLES_IPSET_NAME" "$ip" 2>/dev/null
                }
            else
                iptables -I INPUT -s "$ip" -j DROP 2>/dev/null || {
                    echo "ERROR: Failed to block $ip with iptables" >&2
                    return 1
                }
            fi
            ;;
        ufw)
            ufw deny from "$ip" 2>/dev/null || {
                echo "ERROR: Failed to block $ip in UFW" >&2
                return 1
            }
            ;;
        plesk)
            # Plesk firewall (when enabled)
            if [ -x "$SYS_PLESK_FW_CMD" ]; then
                "$SYS_PLESK_FW_CMD" -S add-rule -rule_name "Block_$ip" -rule_enable true \
                    -client_name all -remote_address "$ip" -action drop 2>/dev/null || {
                    echo "ERROR: Failed to block $ip in Plesk firewall" >&2
                    return 1
                }
            fi
            ;;
        *)
            echo "ERROR: No firewall configured for IP blocking" >&2
            return 1
            ;;
    esac

    return 0
}

# Unblock an IP across the detected firewall
firewall_unblock_ip() {
    local ip="$1"

    if [ -z "$ip" ]; then
        echo "ERROR: IP address required" >&2
        return 1
    fi

    case "$SYS_FIREWALL" in
        csf)
            csf -ar "$ip" 2>/dev/null || {
                echo "ERROR: Failed to unblock $ip in CSF" >&2
                return 1
            }
            ;;
        firewalld)
            firewall-cmd --permanent --remove-rich-rule="rule family=\"ipv4\" source address=\"$ip\" reject" 2>/dev/null
            firewall-cmd --reload 2>/dev/null
            ;;
        iptables)
            if command -v ipset &>/dev/null; then
                ipset del "$SYS_IPTABLES_IPSET_NAME" "$ip" 2>/dev/null || true
            else
                iptables -D INPUT -s "$ip" -j DROP 2>/dev/null || true
            fi
            ;;
        ufw)
            ufw delete deny from "$ip" 2>/dev/null || true
            ;;
        plesk)
            if [ -x "$SYS_PLESK_FW_CMD" ]; then
                "$SYS_PLESK_FW_CMD" -S remove-rule -rule_name "Block_$ip" 2>/dev/null || true
            fi
            ;;
    esac

    return 0
}

# Check if an IP is currently blocked
firewall_is_blocked() {
    local ip="$1"

    if [ -z "$ip" ]; then
        echo "ERROR: IP address required" >&2
        return 1
    fi

    case "$SYS_FIREWALL" in
        csf)
            grep -q "^$ip" "$SYS_CSF_DENY" 2>/dev/null && return 0 || return 1
            ;;
        firewalld)
            firewall-cmd --list-rich-rules 2>/dev/null | grep -q "source address=\"$ip\"" && return 0 || return 1
            ;;
        iptables)
            if command -v ipset &>/dev/null; then
                ipset test "$SYS_IPTABLES_IPSET_NAME" "$ip" 2>/dev/null && return 0 || return 1
            else
                iptables -C INPUT -s "$ip" -j DROP 2>/dev/null && return 0 || return 1
            fi
            ;;
        ufw)
            ufw status numbered 2>/dev/null | grep -q "Deny.*from $ip" && return 0 || return 1
            ;;
        *)
            return 1
            ;;
    esac
}

# Bulk block multiple IPs (format: one IP per line, or space-separated)
firewall_bulk_block_ips() {
    local ips="$1"
    local blocked_count=0
    local failed_count=0

    case "$SYS_FIREWALL" in
        csf)
            while IFS= read -r ip; do
                [ -z "$ip" ] && continue
                if firewall_block_ip "$ip"; then
                    ((blocked_count++))
                else
                    ((failed_count++))
                fi
            done <<< "$ips"
            ;;
        firewalld)
            # Use richd rules for bulk blocks
            while IFS= read -r ip; do
                [ -z "$ip" ] && continue
                if firewall_block_ip "$ip"; then
                    ((blocked_count++))
                else
                    ((failed_count++))
                fi
            done <<< "$ips"
            firewall-cmd --reload 2>/dev/null
            ;;
        iptables)
            # Use ipset for efficient bulk blocking
            if command -v ipset &>/dev/null; then
                ipset create "$SYS_IPTABLES_IPSET_NAME" hash:ip 2>/dev/null || true
                while IFS= read -r ip; do
                    [ -z "$ip" ] && continue
                    if ipset add "$SYS_IPTABLES_IPSET_NAME" "$ip" 2>/dev/null; then
                        ((blocked_count++))
                    else
                        ((failed_count++))
                    fi
                done <<< "$ips"
                # Add rule if not already present
                iptables -C INPUT -m set --match-set "$SYS_IPTABLES_IPSET_NAME" src -j DROP 2>/dev/null || \
                    iptables -I INPUT -m set --match-set "$SYS_IPTABLES_IPSET_NAME" src -j DROP 2>/dev/null
            else
                while IFS= read -r ip; do
                    [ -z "$ip" ] && continue
                    if firewall_block_ip "$ip"; then
                        ((blocked_count++))
                    else
                        ((failed_count++))
                    fi
                done <<< "$ips"
            fi
            ;;
        ufw)
            while IFS= read -r ip; do
                [ -z "$ip" ] && continue
                if firewall_block_ip "$ip"; then
                    ((blocked_count++))
                else
                    ((failed_count++))
                fi
            done <<< "$ips"
            ;;
    esac

    echo "Blocked: $blocked_count, Failed: $failed_count"
    return 0
}

#############################################################################
# MAIN DERIVATION FUNCTION
#############################################################################

derive_all_firewall_operations() {
    case "$SYS_FIREWALL" in
        csf)
            derive_csf_operations
            ;;
        firewalld)
            derive_firewalld_operations
            ;;
        iptables)
            derive_iptables_operations
            ;;
        ufw)
            derive_ufw_operations
            ;;
        *)
            # Check for Imunify even if other firewall is detected
            if command -v imunify360-agent &>/dev/null; then
                derive_imunify_operations
            fi
            # Check for Plesk firewall on Plesk systems
            if [ "$SYS_CONTROL_PANEL" = "plesk" ] && [ -x "$SYS_PLESK_FW_CMD" ] 2>/dev/null; then
                derive_plesk_firewall_operations
            fi
            ;;
    esac
}

# Export functions
export -f firewall_block_ip
export -f firewall_unblock_ip
export -f firewall_is_blocked
export -f firewall_bulk_block_ips

# Auto-run if sourced with detection complete
if [ -n "${SYS_DETECTION_COMPLETE:-}" ]; then
    derive_all_firewall_operations
fi
