#!/usr/bin/env bash

#########################################################################################
#### network-diag.sh — One-shot network diagnostics dump for Linux servers           ####
#### Shows interfaces, routes, DNS, firewall rules, listening ports, connections      ####
####                                                                                 ####
#### Author: Phil Connor                                                             ####
#### Contact: contact@mylinux.work                                                   ####
#### License: MIT                                                                    ####
#### Version 1.00                                                                    ####
####                                                                                 ####
#### Usage:                                                                          ####
####   ./network-diag.sh                                                             ####
####   ./network-diag.sh --section interfaces,dns                                   ####
####                                                                                 ####
#### See --help for all options.                                                     ####
#########################################################################################

set -euo pipefail

# ── Defaults ──────────────────────────────────────────────────────────
SECTIONS="${SECTIONS:-all}"
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"

# ── State ─────────────────────────────────────────────────────────────
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME

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

# ── Logging ───────────────────────────────────────────────────────────
log()     { echo -e "${DIM}[INFO]${RESET} $*"; }
warn()    { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }

# ── Helpers ───────────────────────────────────────────────────────────
section_header() {
    echo ""
    echo -e "  ${BOLD}${CYAN}── $1 ──${RESET}"
    echo ""
}

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

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

should_show() {
    [[ "$SECTIONS" == "all" ]] || [[ ",$SECTIONS," == *",$1,"* ]]
}

# ══════════════════════════════════════════════════════════════════════
#                       INTERFACES
# ══════════════════════════════════════════════════════════════════════

show_interfaces() {
    section_header "Network Interfaces"

    printf "  ${BOLD}%-16s %-18s %-20s %-8s %s${RESET}\n" "INTERFACE" "IP ADDRESS" "MAC" "STATE" "MTU"
    printf "  %s\n" "$(printf '%.0s─' {1..72})"

    if command -v ip &>/dev/null; then
        local ifaces
        ifaces=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | sed 's/@.*//')

        while IFS= read -r iface; do
            [[ -z "$iface" ]] && continue

            local ip_addr mac state mtu
            ip_addr=$(ip -4 -o addr show "$iface" 2>/dev/null | awk '{print $4}' | head -1)
            ip_addr="${ip_addr:-—}"
            mac=$(ip link show "$iface" 2>/dev/null | awk '/link\/ether/ {print $2}')
            mac="${mac:-—}"
            state=$(ip link show "$iface" 2>/dev/null | grep -oP 'state \K\S+' || echo "UNKNOWN")
            mtu=$(ip link show "$iface" 2>/dev/null | grep -oP 'mtu \K\d+' || echo "—")

            local state_color="$DIM"
            if [[ "$state" == "UP" ]]; then
                state_color="$GREEN"
            elif [[ "$state" == "DOWN" ]]; then
                state_color="$RED"
            fi

            printf "  %-16s %-18s %-20s %b%-8s%b %s\n" \
                "$iface" "$ip_addr" "$mac" "$state_color" "$state" "$RESET" "$mtu"
        done <<< "$ifaces"
    else
        warn "ip command not available"
        ifconfig 2>/dev/null || echo "  No interface information available"
    fi

    # IPv6 addresses
    if command -v ip &>/dev/null; then
        local ipv6_count
        ipv6_count=$(ip -6 -o addr show scope global 2>/dev/null | wc -l)
        if [[ "$ipv6_count" -gt 0 ]]; then
            echo ""
            echo -e "  ${BOLD}IPv6 Global Addresses:${RESET}"
            ip -6 -o addr show scope global 2>/dev/null | while IFS= read -r line; do
                local v6_iface v6_addr
                v6_iface=$(echo "$line" | awk '{print $2}')
                v6_addr=$(echo "$line" | awk '{print $4}')
                printf "    %-16s %s\n" "$v6_iface" "$v6_addr"
            done
        fi
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       ROUTES
# ══════════════════════════════════════════════════════════════════════

show_routes() {
    section_header "Routing"

    if command -v ip &>/dev/null; then
        # Default route
        local default_route
        default_route=$(ip route show default 2>/dev/null | head -1)
        if [[ -n "$default_route" ]]; then
            field "Default route:" "$default_route"
        else
            field_color "Default route:" "${YELLOW}None${RESET}"
        fi

        echo ""
        echo -e "  ${BOLD}Routing Table:${RESET}"
        printf "  %-24s %-18s %-12s %s\n" "DESTINATION" "GATEWAY" "DEVICE" "PROTO"
        printf "  %s\n" "$(printf '%.0s─' {1..65})"

        ip route show 2>/dev/null | while IFS= read -r line; do
            local dest gw dev proto
            dest=$(echo "$line" | awk '{print $1}')
            gw=$(echo "$line" | grep -oP 'via \K\S+' || echo "—")
            dev=$(echo "$line" | grep -oP 'dev \K\S+' || echo "—")
            proto=$(echo "$line" | grep -oP 'proto \K\S+' || echo "—")
            printf "  %-24s %-18s %-12s %s\n" "$dest" "$gw" "$dev" "$proto"
        done
    else
        route -n 2>/dev/null || echo "  No routing information available"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       DNS
# ══════════════════════════════════════════════════════════════════════

show_dns() {
    section_header "DNS Configuration"

    # /etc/resolv.conf
    if [[ -f /etc/resolv.conf ]]; then
        echo -e "  ${BOLD}/etc/resolv.conf:${RESET}"

        local nameservers search_domains
        nameservers=$(grep "^nameserver" /etc/resolv.conf 2>/dev/null | awk '{print $2}')
        search_domains=$(grep "^search" /etc/resolv.conf 2>/dev/null | sed 's/^search //')

        if [[ -n "$nameservers" ]]; then
            while IFS= read -r ns; do
                printf "    Nameserver:   %s\n" "$ns"
            done <<< "$nameservers"
        fi

        if [[ -n "${search_domains:-}" ]]; then
            printf "    Search:       %s\n" "$search_domains"
        fi

        # Check if resolv.conf is a symlink (systemd-resolved)
        if [[ -L /etc/resolv.conf ]]; then
            local link_target
            link_target=$(readlink -f /etc/resolv.conf)
            printf "    ${DIM}(symlink → %s)${RESET}\n" "$link_target"
        fi
    fi

    # systemd-resolved
    if command -v resolvectl &>/dev/null; then
        echo ""
        echo -e "  ${BOLD}systemd-resolved:${RESET}"
        resolvectl status 2>/dev/null | grep -E "DNS Server|DNS Domain|DNSSEC" | while IFS= read -r line; do
            printf "    %s\n" "$line"
        done
    elif command -v systemd-resolve &>/dev/null; then
        echo ""
        echo -e "  ${BOLD}systemd-resolved:${RESET}"
        systemd-resolve --status 2>/dev/null | grep -E "DNS Server|DNS Domain|DNSSEC" | while IFS= read -r line; do
            printf "    %s\n" "$line"
        done
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       FIREWALL
# ══════════════════════════════════════════════════════════════════════

show_firewall() {
    section_header "Firewall"

    local fw_found=false

    # iptables
    if command -v iptables &>/dev/null; then
        fw_found=true
        echo -e "  ${BOLD}iptables:${RESET}"

        local ipt_rules
        ipt_rules=$(iptables -S 2>/dev/null | wc -l || echo "0")
        printf "    Rules: %d\n" "$ipt_rules"

        if [[ "$VERBOSE" == "true" ]]; then
            iptables -L -n --line-numbers 2>/dev/null | while IFS= read -r line; do
                printf "    %s\n" "$line"
            done
        else
            # Summary by chain
            iptables -S 2>/dev/null | awk '/^-A/ {print $2}' | sort | uniq -c | sort -rn | while IFS= read -r line; do
                printf "    %s\n" "$line"
            done
        fi
    fi

    # nftables
    if command -v nft &>/dev/null; then
        fw_found=true
        echo ""
        echo -e "  ${BOLD}nftables:${RESET}"

        local nft_tables
        nft_tables=$(nft list tables 2>/dev/null | wc -l || echo "0")
        printf "    Tables: %d\n" "$nft_tables"

        if [[ "$VERBOSE" == "true" ]]; then
            nft list ruleset 2>/dev/null | while IFS= read -r line; do
                printf "    %s\n" "$line"
            done
        else
            nft list tables 2>/dev/null | while IFS= read -r line; do
                printf "    %s\n" "$line"
            done
        fi
    fi

    # ufw
    if command -v ufw &>/dev/null; then
        fw_found=true
        echo ""
        echo -e "  ${BOLD}UFW:${RESET}"
        local ufw_status
        ufw_status=$(ufw status 2>/dev/null | head -1 || echo "Unknown")
        printf "    %s\n" "$ufw_status"
    fi

    # firewalld
    if command -v firewall-cmd &>/dev/null; then
        fw_found=true
        echo ""
        echo -e "  ${BOLD}firewalld:${RESET}"
        local fwd_state
        fwd_state=$(firewall-cmd --state 2>/dev/null || echo "not running")
        printf "    State: %s\n" "$fwd_state"

        if [[ "$fwd_state" == "running" ]]; then
            local active_zones
            active_zones=$(firewall-cmd --get-active-zones 2>/dev/null | head -5)
            if [[ -n "$active_zones" ]]; then
                printf "    Active zones:\n"
                echo "$active_zones" | while IFS= read -r line; do
                    printf "      %s\n" "$line"
                done
            fi
        fi
    fi

    if [[ "$fw_found" == "false" ]]; then
        log "No firewall tools detected (iptables, nftables, ufw, firewalld)"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       LISTENING PORTS
# ══════════════════════════════════════════════════════════════════════

show_ports() {
    section_header "Listening Ports"

    if command -v ss &>/dev/null; then
        printf "  ${BOLD}%-8s %-30s %-8s %s${RESET}\n" "PROTO" "LISTEN ADDRESS" "PORT" "PROCESS"
        printf "  %s\n" "$(printf '%.0s─' {1..70})"

        ss -tlnp 2>/dev/null | tail -n +2 | while IFS= read -r line; do
            local addr port_str proto proc_info
            proto="tcp"
            addr=$(echo "$line" | awk '{print $4}')
            port_str="${addr##*:}"
            proc_info=$(echo "$line" | grep -oP 'users:\(\("\K[^"]+' || echo "—")
            printf "  %-8s %-30s %-8s %s\n" "$proto" "$addr" "$port_str" "$proc_info"
        done

        # UDP listeners
        local udp_count
        udp_count=$(ss -ulnp 2>/dev/null | tail -n +2 | wc -l)
        if [[ "$udp_count" -gt 0 ]]; then
            echo ""
            ss -ulnp 2>/dev/null | tail -n +2 | while IFS= read -r line; do
                local addr port_str proc_info
                addr=$(echo "$line" | awk '{print $4}')
                port_str="${addr##*:}"
                proc_info=$(echo "$line" | grep -oP 'users:\(\("\K[^"]+' || echo "—")
                printf "  %-8s %-30s %-8s %s\n" "udp" "$addr" "$port_str" "$proc_info"
            done
        fi

        local total_tcp total_udp
        total_tcp=$(ss -tlnp 2>/dev/null | tail -n +2 | wc -l)
        total_udp=$(ss -ulnp 2>/dev/null | tail -n +2 | wc -l)
        echo ""
        field "TCP listeners:" "$total_tcp"
        field "UDP listeners:" "$total_udp"
    elif command -v netstat &>/dev/null; then
        netstat -tlnp 2>/dev/null | while IFS= read -r line; do
            printf "  %s\n" "$line"
        done
    else
        warn "Neither ss nor netstat available"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       CONNECTIONS
# ══════════════════════════════════════════════════════════════════════

show_connections() {
    section_header "Active Connections"

    if command -v ss &>/dev/null; then
        echo -e "  ${BOLD}Connections by state:${RESET}"
        printf "  %-20s %s\n" "STATE" "COUNT"
        printf "  %s\n" "$(printf '%.0s─' {1..30})"

        ss -tan 2>/dev/null | tail -n +2 | awk '{print $1}' | sort | uniq -c | sort -rn | while IFS= read -r line; do
            local count state
            count=$(echo "$line" | awk '{print $1}')
            state=$(echo "$line" | awk '{print $2}')

            local color=""
            case "$state" in
                ESTAB)      color="$GREEN" ;;
                TIME-WAIT)  color="$YELLOW" ;;
                CLOSE-WAIT) color="$RED" ;;
                *)          color="" ;;
            esac

            printf "  %b%-20s%b %s\n" "$color" "$state" "$RESET" "$count"
        done

        local total_conn
        total_conn=$(ss -tan 2>/dev/null | tail -n +2 | wc -l)
        echo ""
        field "Total connections:" "$total_conn"
    elif command -v netstat &>/dev/null; then
        netstat -tan 2>/dev/null | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -rn | while IFS= read -r line; do
            printf "  %s\n" "$line"
        done
    else
        warn "Neither ss nor netstat available"
    fi
}

# ══════════════════════════════════════════════════════════════════════
#                       USAGE
# ══════════════════════════════════════════════════════════════════════

usage() {
    cat <<EOF
${SCRIPT_NAME} — One-shot network diagnostics dump for Linux servers

USAGE:
  ${SCRIPT_NAME} [OPTIONS]

OPTIONS:
  --section SECTIONS    Comma-separated sections to show (default: all)
                        Available: interfaces, routes, dns, firewall, ports,
                        connections
  --verbose             Enable debug output (show full firewall rules)
  --no-color            Disable colored output
  --help                Show this help

ENVIRONMENT VARIABLES:
  SECTIONS              Sections to display (default: all)
  COLOR                 Color mode: auto, always, never (default: auto)

EXAMPLES:
  # Full network dump
  ./network-diag.sh

  # Interfaces and DNS only
  ./network-diag.sh --section interfaces,dns

  # Verbose firewall rules
  ./network-diag.sh --section firewall --verbose

  # Pipe-friendly
  ./network-diag.sh --no-color | tee /tmp/netdiag-\$(hostname).txt
EOF
}

# ══════════════════════════════════════════════════════════════════════
#                       ARGUMENT PARSING
# ══════════════════════════════════════════════════════════════════════

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --section)
                SECTIONS="$2"; shift 2 ;;
            --verbose)
                VERBOSE="true"; shift ;;
            --no-color)
                COLOR="never"; shift ;;
            --help|-h)
                setup_colors
                usage
                exit 0 ;;
            *)
                echo "Unknown option: $1" >&2
                echo "Run ${SCRIPT_NAME} --help for usage" >&2
                exit 1 ;;
        esac
    done
}

# ══════════════════════════════════════════════════════════════════════
#                       MAIN
# ══════════════════════════════════════════════════════════════════════

main() {
    parse_args "$@"
    setup_colors

    echo ""
    echo -e "${BOLD}Network Diagnostics — $(hostname -f 2>/dev/null || hostname)${RESET}"
    echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"

    should_show "interfaces"  && show_interfaces
    should_show "routes"      && show_routes
    should_show "dns"         && show_dns
    should_show "firewall"    && show_firewall
    should_show "ports"       && show_ports
    should_show "connections" && show_connections

    echo ""
}

main "$@"
