#!/bin/bash

################################################################################
# FreeRADIUS Installation and LDAP Integration Script for Debian 13
################################################################################
# Description: Interactive deployment script for FreeRADIUS with LDAP support
# Author: AI Assistant
# Version: 1.0
# Date: 2026-02-25
################################################################################

set -e  # Exit on error

################################################################################
# Color codes for output
################################################################################
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

################################################################################
# Global variables
################################################################################
BACKUP_DIR="/var/backups/freeradius-$(date +%Y%m%d-%H%M%S)"
LDAP_TYPE=""
LDAP_SERVER=""
LDAP_BASE_DN=""
LDAP_BIND_DN=""
LDAP_BIND_PASSWORD=""
RADIUS_SECRET=""
CLIENT_IP=""
NON_INTERACTIVE=false
SKIP_TESTING=false

################################################################################
# Print functions
################################################################################
print_header() {
    echo -e "\n${BLUE}========================================${NC}"
    echo -e "${BLUE}$1${NC}"
    echo -e "${BLUE}========================================${NC}\n"
}

print_success() {
    echo -e "${GREEN}✓ $1${NC}"
}

print_error() {
    echo -e "${RED}✗ $1${NC}"
}

print_warning() {
    echo -e "${YELLOW}⚠ $1${NC}"
}

print_info() {
    echo -e "${BLUE}ℹ $1${NC}"
}

print_step() {
    echo -e "\n${YELLOW}>>> $1${NC}\n"
}

################################################################################
# Help function
################################################################################
show_help() {
    cat << EOF
FreeRADIUS Installation and LDAP Integration Script

Usage: $0 [OPTIONS]

Options:
  --help              Show this help message
  --no-interactive     Run with default values (not recommended)
  --backup-dir PATH     Specify custom backup directory
  --skip-testing       Skip post-installation testing
  --ldap-type TYPE     Force LDAP type (openldap/ad)

Examples:
  $0                          # Interactive installation
  $0 --skip-testing            # Skip testing phase
  $0 --ldap-type openldap       # Force OpenLDAP integration
  $0 --backup-dir /my/backup    # Custom backup directory

EOF
    exit 0
}

################################################################################
# Parse command line arguments
################################################################################
parse_arguments() {
    while [[ $# -gt 0 ]]; do
        case $1 in
            --help)
                show_help
                ;;
            --no-interactive)
                NON_INTERACTIVE=true
                shift
                ;;
            --backup-dir)
                BACKUP_DIR="$2"
                shift 2
                ;;
            --skip-testing)
                SKIP_TESTING=true
                shift
                ;;
            --ldap-type)
                LDAP_TYPE="$2"
                shift 2
                ;;
            *)
                print_error "Unknown option: $1"
                show_help
                ;;
        esac
    done
}

################################################################################
# Check if running as root
################################################################################
check_root() {
    if [[ $EUID -ne 0 ]]; then
        print_error "This script must be run as root (use sudo)"
        exit 1
    fi
}

################################################################################
# Check if Debian 13
################################################################################
check_debian_version() {
    print_step "Checking Debian version"
    if [[ ! -f /etc/debian_version ]]; then
        print_error "This script is designed for Debian only"
        exit 1
    fi

    DEB_VERSION=$(cat /etc/debian_version)
    print_info "Detected Debian version: $DEB_VERSION"
}

################################################################################
# Backup existing configuration
################################################################################
backup_config() {
    print_step "Backing up existing configuration"
    mkdir -p "$BACKUP_DIR"

    if [[ -d /etc/freeradius ]]; then
        cp -r /etc/freeradius "$BACKUP_DIR/"
        print_success "FreeRADIUS configuration backed up"
    fi

    if [[ -f /etc/samba/smb.conf ]]; then
        cp /etc/samba/smb.conf "$BACKUP_DIR/"
        print_success "Samba configuration backed up"
    fi

    if [[ -f /etc/krb5.conf ]]; then
        cp /etc/krb5.conf "$BACKUP_DIR/"
        print_success "Kerberos configuration backed up"
    fi

    print_info "Backup directory: $BACKUP_DIR"
}

################################################################################
# Update system packages
################################################################################
update_system() {
    print_step "Updating system packages"
    apt update
    print_success "Package list updated"
}

################################################################################
# Install FreeRADIUS
################################################################################
install_freeradius() {
    print_step "Installing FreeRADIUS and utilities"
    apt install -y freeradius freeradius-utils freeradius-ldap
    print_success "FreeRADIUS installed"
}

################################################################################
# Configure RADIUS clients
################################################################################
configure_clients() {
    print_step "Configuring RADIUS clients"

    if $NON_INTERACTIVE; then
        RADIUS_SECRET="testing123"
        CLIENT_IP="127.0.0.1"
    else
        read -p "Enter RADIUS shared secret (default: testing123): " RADIUS_SECRET
        RADIUS_SECRET=${RADIUS_SECRET:-testing123}

        read -p "Enter client IP address (default: 127.0.0.1): " CLIENT_IP
        CLIENT_IP=${CLIENT_IP:-127.0.0.1}

        read -p "Enter client shortname (default: localhost): " CLIENT_SHORTNAME
        CLIENT_SHORTNAME=${CLIENT_SHORTNAME:-localhost}
    fi

    # Stop FreeRADIUS before modifying config
    systemctl stop freeradius 2>/dev/null || true

    # Backup and create new clients.conf
    cp /etc/freeradius/clients.conf /etc/freeradius/clients.conf.bak

    cat > /etc/freeradius/clients.conf << EOF
client localhost {
    ipaddr = 127.0.0.1
    secret = ${RADIUS_SECRET}
    shortname = localhost
}

client configured_client {
    ipaddr = ${CLIENT_IP}
    secret = ${RADIUS_SECRET}
    shortname = ${CLIENT_SHORTNAME:-configured_client}
}
EOF

    print_success "RADIUS clients configured"
}

################################################################################
# Add test user
################################################################################
add_test_user() {
    print_step "Adding test user"

    # Add test user to authorization file
    AUTH_FILE="/etc/freeradius/mods-config/files/authorize"

    if [[ ! -f "$AUTH_FILE" ]]; then
        mkdir -p "$(dirname "$AUTH_FILE")"
        touch "$AUTH_FILE"
    fi

    # Check if test user already exists
    if ! grep -q "^testing Cleartext-Password" "$AUTH_FILE"; then
        # Add test user at the beginning of the file
        sed -i '1itest Cleartext-Password := "password"\n' "$AUTH_FILE"
        print_success "Test user added"
    else
        print_info "Test user already exists"
    fi
}

################################################################################
# Detect or select LDAP type
################################################################################
select_ldap_type() {
    print_step "Selecting LDAP type"

    if [[ -n "$LDAP_TYPE" ]]; then
        print_info "LDAP type forced: $LDAP_TYPE"
        return
    fi

    if $NON_INTERACTIVE; then
        LDAP_TYPE="openldap"
        print_info "Using default LDAP type: OpenLDAP"
        return
    fi

    echo "Select LDAP integration type:"
    echo "1) OpenLDAP"
    echo "2) Active Directory"
    echo "3) No LDAP (local users only)"
    read -p "Enter choice [1-3] (default: 1): " LDAP_CHOICE
    LDAP_CHOICE=${LDAP_CHOICE:-1}

    case $LDAP_CHOICE in
        1)
            LDAP_TYPE="openldap"
            print_success "Selected: OpenLDAP"
            ;;
        2)
            LDAP_TYPE="ad"
            print_success "Selected: Active Directory"
            ;;
        3)
            LDAP_TYPE="none"
            print_info "Selected: Local users only"
            ;;
        *)
            print_error "Invalid choice"
            exit 1
            ;;
    esac
}

################################################################################
# Configure OpenLDAP integration
################################################################################
configure_openldap() {
    print_step "Configuring OpenLDAP integration"

    if $NON_INTERACTIVE; then
        LDAP_SERVER="localhost"
        LDAP_BASE_DN="dc=example,dc=org"
        LDAP_BIND_DN="cn=admin,dc=example,dc=org"
        LDAP_BIND_PASSWORD="admin"
    else
        read -p "Enter LDAP server (default: localhost): " LDAP_SERVER
        LDAP_SERVER=${LDAP_SERVER:-localhost}

        read -p "Enter LDAP base DN (default: dc=example,dc=org): " LDAP_BASE_DN
        LDAP_BASE_DN=${LDAP_BASE_DN:-dc=example,dc=org}

        read -p "Enter LDAP bind DN (default: cn=admin,dc=example,dc=org): " LDAP_BIND_DN
        LDAP_BIND_DN=${LDAP_BIND_DN:-cn=admin,dc=example,dc=org}

        read -sp "Enter LDAP bind password: " LDAP_BIND_PASSWORD
        echo
    fi

    # Check if OpenLDAP is installed
    if ! command -v ldapsearch &> /dev/null; then
        print_warning "OpenLDAP not installed. Installing now..."
        apt install -y slapd ldap-utils
    fi

    # Enable LDAP module
    ln -sf /etc/freeradius/mods-available/ldap /etc/freeradius/mods-enabled/ldap

    # Configure LDAP module
    cat > /etc/freeradius/mods-enabled/ldap << EOF
ldap {
    server = '${LDAP_SERVER}'
    identity = '${LDAP_BIND_DN}'
    password = ${LDAP_BIND_PASSWORD}

    base_dn = '${LDAP_BASE_DN}'
    sasl = {
        mech = 'PLAIN'
    }

    update {
        control:Password-With-Header += 'userPassword'
        control:NT-Password += 'ntPassword'
    }
}
EOF

    # Update default site to use LDAP
    DEFAULT_SITE="/etc/freeradius/sites-enabled/default"
    if [[ -f "$DEFAULT_SITE" ]]; then
        # Add ldap to authorize section
        if ! grep -q "^[[:space:]]*ldap" "$DEFAULT_SITE"; then
            sed -i '/^[[:space:]]*authorize {/,/^[[:space:]]*}/ s/}/\n    ldap\n}/' "$DEFAULT_SITE"
        fi

        # Add LDAP authenticate section
        if ! grep -q "Auth-Type LDAP" "$DEFAULT_SITE"; then
            sed -i '/^[[:space:]]*authenticate {/,/^[[:space:]]*}/ a\
\    Auth-Type LDAP {\
        ldap\
    }' "$DEFAULT_SITE"
        fi
    fi

    print_success "OpenLDAP integration configured"
}

################################################################################
# Configure Active Directory integration
################################################################################
configure_active_directory() {
    print_step "Configuring Active Directory integration"

    if $NON_INTERACTIVE; then
        print_warning "Non-interactive AD configuration not recommended"
        LDAP_SERVER="ad-server.yourdomain.com"
        LDAP_BASE_DN="dc=yourdomain,dc=com"
        LDAP_BIND_DN="Administrator"
    else
        read -p "Enter AD server hostname (e.g., ad-server.yourdomain.com): " LDAP_SERVER
        LDAP_SERVER=${LDAP_SERVER:-ad-server.yourdomain.com}

        read -p "Enter AD domain (e.g., YOURDOMAIN): " AD_DOMAIN
        AD_DOMAIN=${AD_DOMAIN:-YOURDOMAIN}

        read -p "Enter AD realm (e.g., YOURDOMAIN.COM): " AD_REALM
        AD_REALM=${AD_REALM:-YOURDOMAIN.COM}

        read -p "Enter Administrator username (default: Administrator): " AD_ADMIN
        AD_ADMIN=${AD_ADMIN:-Administrator}

        read -sp "Enter Administrator password: " AD_PASSWORD
        echo
    fi

    # Install Samba and winbind
    apt install -y samba winbind krb5-config libsasl2-modules-ldap

    # Configure Samba
    cat > /etc/samba/smb.conf << EOF
[global]
    workgroup = ${AD_DOMAIN}
    security = ads
    realm = ${AD_REALM}
    password server = ${LDAP_SERVER}
    idmap config * : backend = tdb
    idmap config * : range = 2000-9999
    idmap config ${AD_DOMAIN} : backend = rid
    idmap config ${AD_DOMAIN} : range = 10000-99999
    winbind use default domain = yes
    winbind nss info = rfc2307
    template shell = /bin/bash
    winbind offline logon = yes
    ntlm auth = mschapv2-and-ntlmv2-only
EOF

    # Configure Kerberos
    cat > /etc/krb5.conf << EOF
[libdefaults]
    default_realm = ${AD_REALM}
    dns_lookup_realm = false
    dns_lookup_kdc = true

[realms]
    ${AD_REALM} = {
        kdc = ${LDAP_SERVER}
        admin_server = ${LDAP_SERVER}
    }

[domain_realm]
    .${AD_REALM,,} = ${AD_REALM}
    ${AD_REALM,,} = ${AD_REALM}
EOF

    # Configure NSS
    sed -i 's/^passwd:.*/passwd:         files winbind systemd/' /etc/nsswitch.conf
    sed -i 's/^group:.*/group:          files winbind systemd/' /etc/nsswitch.conf

    print_info "Attempting to join AD domain..."
    print_warning "This may take a few moments..."

    # Attempt to join domain
    echo "$AD_PASSWORD" | net ads join -U "$AD_ADMIN" 2>&1 || {
        print_error "Failed to join AD domain. Please check credentials and network connectivity."
        print_info "You can join manually later with: sudo net ads join -U Administrator"
        return 1
    }

    # Set winbind permissions
    if [[ -d /var/run/samba/winbindd_privileged ]]; then
        setfacl -m u:freerad:rx /var/run/samba/winbindd_privileged/ 2>/dev/null || {
            print_warning "Could not set ACL permissions on winbind socket"
        }
    fi

    # Configure MS-CHAP module
    cat > /etc/freeradius/mods-enabled/mschap << EOF
mschap {
    with_ntdomain_hack = yes
    ntlm_auth = "/usr/bin/ntlm_auth --request-nt-key --username=%{mschap:User-Name} --domain=%{mschap:NT-Domain:-${AD_DOMAIN}} --challenge=%{mschap:Challenge:-00} --nt-response=%{mschap:NT-Response:-00}"
}
EOF

    print_success "Active Directory integration configured"
}

################################################################################
# Test basic FreeRADIUS configuration
################################################################################
test_freeradius() {
    print_step "Testing FreeRADIUS configuration"

    # Start FreeRADIUS in debug mode
    print_info "Starting FreeRADIUS in debug mode..."
    timeout 5 freeradius -X &> /tmp/freeradius-test.log &
    FR_PID=$!

    sleep 2

    # Check if it's still running
    if ps -p $FR_PID > /dev/null; then
        print_success "FreeRADIUS started successfully"

        # Test authentication
        if command -v radtest &> /dev/null; then
            print_info "Testing authentication with test user..."
            if radtest testing password 127.0.0.1 0 testing123 2>&1 | grep -q "Access-Accept"; then
                print_success "Authentication test passed"
            else
                print_warning "Authentication test failed"
                print_info "Debug log: /tmp/freeradius-test.log"
            fi
        else
            print_warning "radtest not available for testing"
        fi

        # Stop FreeRADIUS
        kill $FR_PID 2>/dev/null || true
        wait $FR_PID 2>/dev/null || true
    else
        print_error "FreeRADIUS failed to start"
        print_info "Debug log: /tmp/freeradius-test.log"
        cat /tmp/freeradius-test.log
        return 1
    fi
}

################################################################################
# Test LDAP connectivity
################################################################################
test_ldap() {
    if [[ "$LDAP_TYPE" == "none" ]]; then
        return 0
    fi

    print_step "Testing LDAP connectivity"

    if [[ "$LDAP_TYPE" == "openldap" ]]; then
        if command -v ldapsearch &> /dev/null; then
            print_info "Testing OpenLDAP connection..."
            if ldapsearch -x -H "ldap://${LDAP_SERVER}" -D "$LDAP_BIND_DN" -w "$LDAP_BIND_PASSWORD" -b "$LDAP_BASE_DN" -s base "(objectclass=*)" 2>&1 | grep -q "dn:"; then
                print_success "OpenLDAP connection successful"
            else
                print_warning "Could not connect to OpenLDAP server"
                print_info "Please verify: server, bind DN, and password"
            fi
        fi
    elif [[ "$LDAP_TYPE" == "ad" ]]; then
        print_info "Testing Active Directory connection..."
        if wbinfo -t 2>&1 | grep -q "checking the trust secret via RPC calls succeeded"; then
            print_success "Active Directory connection successful"
        else
            print_warning "Could not verify AD connection"
            print_info "Please verify: domain join, winbind status"
        fi
    fi
}

################################################################################
# Start FreeRADIUS service
################################################################################
start_service() {
    print_step "Starting FreeRADIUS service"

    systemctl enable freeradius
    systemctl start freeradius

    sleep 2

    if systemctl is-active --quiet freeradius; then
        print_success "FreeRADIUS service started and enabled"
    else
        print_error "Failed to start FreeRADIUS service"
        print_info "Check logs with: journalctl -u freeradius -n 50"
        return 1
    fi
}

################################################################################
# Print summary
################################################################################
print_summary() {
    cat << EOF

${GREEN}========================================
Installation Summary
========================================${NC}

${BLUE}FreeRADIUS Version:${NC}
    $(freeradius -v 2>&1 | head -1)

${BLUE}Configuration:${NC}
    Clients configured for: ${CLIENT_IP:-127.0.0.1}
    Test user: testing / password

${BLUE}LDAP Integration:${NC}
EOF

    case $LDAP_TYPE in
        openldap)
            echo "    Type: OpenLDAP"
            echo "    Server: ${LDAP_SERVER:-localhost}"
            echo "    Base DN: ${LDAP_BASE_DN:-dc=example,dc=org}"
            ;;
        ad)
            echo "    Type: Active Directory"
            echo "    Domain: ${AD_DOMAIN:-YOURDOMAIN}"
            ;;
        none)
            echo "    Type: None (local users only)"
            ;;
    esac

    cat << EOF

${BLUE}Backup Location:${NC}
    ${BACKUP_DIR}

${BLUE}Next Steps:${NC}
    1. Review configuration files in /etc/freeradius/
    2. Add real users to your LDAP directory
    3. Configure your NAS devices with shared secret
    4. Test with real clients
    5. Review logs: journalctl -u freeradius -f

${BLUE}Useful Commands:${NC}
    - Test auth: radtest testing password 127.0.0.1 0 ${RADIUS_SECRET:-testing123}
    - Check status: systemctl status freeradius
    - Debug mode: freeradius -X
    - View logs: journalctl -u freeradius -f

${BLUE}Documentation:${NC}
    https://your-server.com/freeradius/

${GREEN}========================================${NC}

EOF
}

################################################################################
# Main execution flow
################################################################################
main() {
    print_header "FreeRADIUS Installation and LDAP Integration"

    # Parse arguments
    parse_arguments "$@"

    # Pre-flight checks
    check_root
    check_debian_version

    # Installation
    update_system
    install_freeradius

    # Configuration
    backup_config
    configure_clients
    add_test_user

    # LDAP integration
    select_ldap_type
    case $LDAP_TYPE in
        openldap)
            configure_openldap
            ;;
        ad)
            configure_active_directory || print_warning "AD integration may need manual completion"
            ;;
        none)
            print_info "No LDAP integration configured"
            ;;
    esac

    # Testing
    if ! $SKIP_TESTING; then
        test_freeradius
        test_ldap
    else
        print_warning "Testing skipped as requested"
    fi

    # Start service
    start_service

    # Summary
    print_summary
}

# Run main function
main "$@"
