#!/usr/bin/env bash

set -euo pipefail
IFS=$'\n\t'

# Constants
readonly NIXOS_RELEASE="24.11"                                      # Home Manager release version (Must match NixOS version)           
readonly GIT_REPO="https://git.mvl.sh/vleeuwenmenno/dotfiles.git"   # Dotfiles repository URL
readonly DOTFILES_PATH="${HOME}/.dotfiles"                            # Dotfiles directory
readonly SETUP_MARKER="${HOME}/.dotfiles-setup"                     # Setup marker file indicates setup has been run

# Color constants
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[0;33m'
readonly NC='\033[0m' # No Color

# Helper functions
log_info() {
    echo -e "${YELLOW}$1${NC}"
}

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

log_error() {
    echo -e "${RED}$1${NC}" >&2
}

log_warning() {
    echo -e "${YELLOW}$1${NC}" >&2
}

die() {
    log_error "$1"
    exit 1
}

# Ensure we're running interactively
ensure_interactive() {
    # If stdin is not a terminal, reconnect stdin to /dev/tty
    if [ ! -t 0 ]; then
        exec < /dev/tty || die "Failed to connect to terminal. Please run the script directly instead of piping from curl"
    fi
}

confirm_symlink() {
    local link="$1"
    local msg="$2"
    if [ ! -L "$link" ]; then
        die "$msg"
    fi
}

backup_file() {
    local file="$1"
    local need_sudo="${2:-false}"

    if [ -f "$file" ]; then
        log_info "Backing up $file to $file.bak..."
        if [ "$need_sudo" = "true" ]; then
            sudo mv "$file" "$file.bak" || die "Failed to backup $file (sudo)"
        else
            mv "$file" "$file.bak" || die "Failed to backup $file"
        fi
    fi
}


check_prerequisites() {
    command -v git >/dev/null 2>&1 || die "Git is required but not installed"
    command -v sudo >/dev/null 2>&1 || die "Sudo is required but not installed"
}

validate_hostname() {
    local hostname="$1"
    if [[ -z "$hostname" || ! "$hostname" =~ ^[a-zA-Z0-9_-]+$ || ${#hostname} -gt 64 ]]; then
        return 1
    fi
    return 0
}

update_home_manager_flake() {
    local hostname="$1"
    local isServer="$2"
    local flake_file="$DOTFILES_PATH/config/home-manager/flake.nix"
    
    # Create new configuration entry
    local new_config="        \"$hostname\" = home-manager.lib.homeManagerConfiguration {
          inherit pkgs;
          modules = [ ./home.nix ];
          extraSpecialArgs = {
            inherit pkgs pkgs-unstable;
            isServer = $isServer;
            hostname = \"$hostname\";
          };
        };
        "

    # Create temporary file
    local temp_file=$(mktemp)
    
    # Find the line number where homeConfigurations = { appears
    local config_line=$(grep -n "homeConfigurations = {" "$flake_file" | cut -d: -f1)
    
    if [ -z "$config_line" ]; then
        rm "$temp_file"
        die "Could not find homeConfigurations in flake.nix"
    fi

    # Copy the file up to the line after homeConfigurations = {
    head -n "$config_line" "$flake_file" > "$temp_file"
    
    # Add the new configuration
    echo "$new_config" >> "$temp_file"
    
    # Add the rest of the file starting from the line after homeConfigurations = {
    tail -n +"$((config_line + 1))" "$flake_file" >> "$temp_file"

    # Validate the new file
    if ! nix-shell -p nixfmt --run "nixfmt $temp_file"; then
        rm "$temp_file"
        return 1
    fi

    # Replace original file
    mv "$temp_file" "$flake_file" || return 1
    log_success "Home Manager Flake configuration added successfully."
}

install_nix() {
    if command -v nix-channel >/dev/null 2>&1; then
        log_success "Detected Nix, skipping Nix setup."
        return 0
    fi

    log_info "Nix not detected, installing Nix..."
    curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix -o install-nix.sh || \
        die "Failed to download Nix installer"
    
    sh install-nix.sh install || die "Failed to install Nix"
    rm install-nix.sh || die "Failed to remove Nix installer"
    . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh || die "Failed to source Nix profile"
}

setup_symlinks() {
    log_info "Setting up symlinks..."

    # Backup and create symlinks for user files
    backup_file "$HOME/.bashrc"
    backup_file "$HOME/.profile"

    if [ -d "$HOME/.config/home-manager" ]; then
        log_info "Backing up ~/.config/home-manager to ~/.config/home-manager.bak..."
        mv "$HOME/.config/home-manager" "$HOME/.config/home-manager.bak" || \
            die "Failed to backup home-manager config"
    fi

    log_info "Linking ~/.config/home-manager to $DOTFILES_PATH/config/home-manager..."
    ln -s "$DOTFILES_PATH/config/home-manager" "$HOME/.config/home-manager" || \
        die "Failed to create home-manager symlink"

    # Verify symlinks
    confirm_symlink "$HOME/.config/home-manager" "Failed to set up home-manager symlink"
    log_success "Symlinks set up successfully."
}

install_home_manager() {
    if command -v home-manager >/dev/null 2>&1; then
        log_success "Home Manager already installed. Skipping..."
        return 0
    fi

    log_info "Installing Home Manager..."
    nix-channel --add "https://github.com/nix-community/home-manager/archive/release-$NIXOS_RELEASE.tar.gz" home-manager || die "Failed to add home-manager channel"
    nix-channel --update || die "Failed to update channels"
    nix-shell '<home-manager>' -A install || die "Failed to install home-manager"
}

prepare_hostname() {
    local hostname_file="$HOME/.hostname"
    local hostname

    # Only check for NixOS if hardware configuration is not found
    if [ -f "$hostname_file" ]; then
        hostname=$(cat "$hostname_file")
        log_success "Hostname already found in $hostname_file. Using $hostname."
        return
    fi

    # Ensure interactive input before hostname prompt
    ensure_interactive

    while true; do
        log_info "Enter the hostname for this machine:"
        read -r hostname
        if validate_hostname "$hostname"; then
            break
        fi
        log_error "Invalid hostname. Please enter a valid hostname:"
    done

    log_info "Setting hostname to $hostname..."
    sudo hostnamectl set-hostname "$hostname" || die "Failed to set hostname"

    echo "$hostname" > "$hostname_file" || die "Failed to save hostname"
    log_success "Hostname set successfully."
}

warning_prompt() {
    log_success "This script will set up your machine using Menno's Dotfiles repository.\n"
    log_error "Please ensure you have a backup of your data before proceeding."
    log_error "This script will modify system files and may require sudo permissions.\n"
    log_info "This script works best on a fresh Fedora, Ubuntu or Arch Linux installation."
    log_info "Type 'continue' to continue or Ctrl+C to exit."
    read -r -p "> " continue < /dev/tty
    if [ "$continue" != "continue" ]; then
        die "Exiting..."
    fi

    log_info "Starting setup..."
}

check_selinux() {
    # Check if distro has SELinux at all:
    if [ ! -d /etc/selinux ]; then
        log_success "SELinux not found. Skipping..."
        return 0
    fi

    # Check if getenforce is returning Enforcing
    if [ "$(getenforce)" = "Enforcing" ]; then
        log_warning "SELinux is enabled. Adjusting SELinux to permissive mode..."
        sudo setenforce Permissive || die "Failed to disable SELinux"
        sudo tee /etc/selinux/config << EOF > /dev/null || die "Failed to write to /etc/selinux/config"
SELINUX=permissive
SELINUXTYPE=targeted
EOF
        log_success "SELinux disabled successfully."
    fi
}

ensure_pipx() {
    if ! command -v pipx >/dev/null 2>&1; then
        log_info "Installing pipx..."
        
        attempt_package_install "pipx"

        # On Arch Linux, install pipx using pacman
        if command -v pacman >/dev/null 2>&1; then
            sudo pacman -S python-pipx || die "Failed to install pipx"
        fi

        # ensurepath globally
        sudo pipx ensurepath --global || die "Failed to ensure pipx path"
    fi
}

setup_ansible() {
    ensure_pipx

    log_info "Installing Ansible..."
    pipx install ansible || die "Failed to install Ansible"
}

check_command_availibility() {
    local command="$1"
    if ! command -v "$command" >/dev/null 2>&1; then
        die "$command is required but not installed"
    fi
}

attempt_package_install() {
    local package="$1"

    # determine which package manager to use
    local package_manager
    if command -v dnf >/dev/null 2>&1; then
        package_manager="dnf"
    elif command -v apt >/dev/null 2>&1; then
        package_manager="apt"
    elif command -v pacman >/dev/null; then
        package_manager="pacman"
    else
        log_error "No supported package manager was found, aborting setup..."
        exit 1
    fi

    if ! command -v "$package" >/dev/null 2>&1; then
        log_info "Installing $package using $package_manager..."
        if [ "$package_manager" = "dnf" ]; then
            sudo dnf install "$package" -y || die "Failed to install $package"
        elif [ "$package_manager" = "apt" ]; then
            sudo apt install "$package" -y || die "Failed to install $package"
        elif [ "$package_manager" = "pacman" ]; then
            sudo pacman -S "$package" || die "Failed to install $package"
        else
            die "Unsupported package manager: $package_manager"
        fi
    fi
}

# Check compatibility checks for supported distros:
# - Fedora
# - Ubuntu
# - Arch Linux (Untested)
check_compatibility() {
    # Check if we are running under bash
    if [ -z "${BASH_VERSION:-}" ]; then
        die "This script was designed to run using bash, please run using bash"
    fi

    attempt_package_install "awk"
    attempt_package_install "tail"
    attempt_package_install "git"

    local distro
    distro=$(awk -F= '/^NAME/{print $2}' /etc/os-release | tr -d '"')

    case "$distro" in
        Fedora*)
            log_success "Detected Fedora. Proceeding with setup..."
            check_command_availibility "dnf"
            ;;
        Ubuntu)
            log_success "Detected Ubuntu. Proceeding with setup..."
            check_command_availibility "apt"
            ;;
        Arch*)
            log_warning "Detected Arch Linux. Setup has not been tested on Arch Linux."
            log_warning "Proceed at your own risk..."
            check_command_availibility "pacman"
            ;;
        *)
            die "Unsupported distribution: $distro"
            ;;
    esac
}

main() {
    check_compatibility

    # Check if setup has already been run
    if [ -f "$SETUP_MARKER" ]; then
        log_info "Setup has already been run, exiting..."
        exit 0
    fi

    # Check prerequisites
    check_prerequisites

    # Clone dotfiles if needed
    if [ ! -d "$DOTFILES_PATH" ]; then
        log_info "Cloning dotfiles repo..."
        git clone "$GIT_REPO" "$DOTFILES_PATH" || die "Failed to clone dotfiles repository"
    fi
    
    if [ -n "${1:-}" ]; then
        CONTINUE=$1
        if [ "$CONTINUE" = "--continue" ]; then
            log_info "Continuing setup..."
        else
            warning_prompt
            prepare_hostname
            check_selinux
            install_nix
        fi
    else
        warning_prompt
        prepare_hostname
        check_selinux
        install_nix
    fi


    install_home_manager

    setup_symlinks
    setup_ansible

    # Get hostname
    local hostname
    hostname=$(cat "$HOME/.hostname") || die "Failed to read hostname"
    export PATH=$PATH:$DOTFILES_PATH/bin

    # Create new .bashrc with exports for initial `dotf update` command
    echo "export NIXPKGS_ALLOW_INSECURE=1" >> $HOME/.bashrc
    echo "export DOTFILES_PATH=$DOTFILES_PATH" >> $HOME/.bashrc
    echo "export PATH=$PATH:$DOTFILES_PATH/bin" >> $HOME/.bashrc

    # Create setup marker
    touch "$SETUP_MARKER" || die "Failed to create setup marker"

    # Final success message
    log_success "\nSetup complete. Please logout / restart to continue with 'dotf update'.\n"
    log_error "\n!!! Please logout / restart to continue !!!"
    log_error "~~~   Proceed by running 'dotf update'  ~~~\n"
}

main "$@"
