#!/usr/bin/env ruby
# frozen_string_literal: true

# Debian-family helper: install zsh + deps, optionally set default shell and Oh My Zsh.
# Non-interactive (no TTY): set INSTALL_OMZ=yes|no and SET_DEFAULT_ZSH=yes|no; otherwise prompts are skipped (no).
# When INSTALL_OMZ is yes and ~/.oh-my-zsh exists (already or just installed), writes bundled ~/.zshrc (backs up any existing file).

require "etc"
require "open3"
require "shellwords"

class DebianZshSetup
  PACKAGES = %w[zsh git curl].freeze
  OMZ_INSTALL_URL = "https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh"
  OMZ_DIR = File.expand_path("~/.oh-my-zsh")

  # Written when the user opts into Oh My Zsh and ~/.oh-my-zsh exists (fresh or pre-existing).
  CUSTOM_OMZ_ZSHRC = <<~'ZSHRC'
    export ZSH="$HOME/.oh-my-zsh"
    ZSH_THEME="robbyrussell"
    plugins=(git)
    source $ZSH/oh-my-zsh.sh

    HISTFILE=~/.zsh_history
    HISTSIZE=50000
    SAVEHIST=50000
    setopt APPEND_HISTORY
    setopt SHARE_HISTORY
    setopt HIST_IGNORE_ALL_DUPS
    setopt HIST_IGNORE_SPACE
    setopt AUTO_CD
    setopt EXTENDED_GLOB

    if [ -x /usr/bin/dircolors ]; then
      test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
      alias ls='ls --color=auto'
      alias grep='grep --color=auto'
      alias fgrep='fgrep --color=auto'
      alias egrep='egrep --color=auto'
    fi

    alias apt='sudo apt'
    alias cleanlogs='curl -sSL https://raw.githubusercontent.com/hardenedpenguin/asl-misc-scripts/refs/heads/main/cleanup_old_logs.rb | sudo ruby'

    update-system() {
      sudo apt update
      sudo apt full-upgrade -y
      sudo apt autoremove --purge -y
      sudo apt clean
    }

    [[ -f ~/.bash_aliases ]] && . ~/.bash_aliases
    [[ -f ~/.zsh_aliases ]] && . ~/.zsh_aliases

    autoload -Uz compinit
    compinit -d "${ZDOTDIR:-$HOME}/.zcompdump"
    zstyle ':completion:*' matcher-list 'm:{a-z}={A-Z}'

    export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'

    [[ -f /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh ]] && source /usr/share/zsh-autosuggestions/zsh-autosuggestions.zsh
    [[ -f /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]] && source /usr/share/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh

    autoload -U up-line-or-beginning-search
    autoload -U down-line-or-beginning-search
    zle -N up-line-or-beginning-search
    zle -N down-line-or-beginning-search
    bindkey "^[[A" up-line-or-beginning-search
    bindkey "^[[B" down-line-or-beginning-search

    RPROMPT="%F{8}${SSH_TTY:+%n@%m}%f"%
  ZSHRC

  def initialize
    @errors = []
    @omz_selected = false
  end

  def run
    banner
    warn_unless_debianish
    exit 2 unless prerequisites_ok?

    install_packages
    return finalize_failure if @errors.any?

    maybe_set_default_shell
    maybe_install_oh_my_zsh
    write_custom_zshrc_for_omz!
    finalize_success
  end

  private

  def execute(argv)
    puts "→ #{Shellwords.join(argv)}"
    system(*argv)
  end

  def banner
    puts <<~BANNER

      --- Zsh & Oh My Zsh setup (Debian-family) ---
    BANNER
  end

  def warn_unless_debianish
    return if File.readable?("/etc/debian_version")

    warn "Note: /etc/debian_version not found — this script expects apt-based systems."
  end

  def prerequisites_ok?
    unless find_executable("sudo")
      @errors << "sudo not found; run as root or install sudo."
      return false
    end

    unless sudo_preflight
      @errors << "sudo failed (wrong password, not in sudoers, or need a TTY for askpass)."
      return false
    end

    true
  end

  def sudo_preflight
    _, _, status = Open3.capture3("sudo", "-n", "true")
    return true if status.success?

    # Allow passworded sudo when interactive
    return false unless interactive?

    system("sudo", "-v")
  end

  def install_packages
    puts "Updating apt and installing: #{PACKAGES.join(", ")}"
    ok = execute(%w[sudo apt-get update -qq]) && execute(
      %w[sudo apt-get install -y -qq --no-install-recommends] + PACKAGES
    )
    @errors << "apt failed to install required packages." unless ok
  end

  def maybe_set_default_shell
    zsh = zsh_path
    unless zsh
      @errors << "zsh binary not found in PATH after install."
      return
    end

    if current_shell == zsh
      puts "Default shell is already zsh (#{zsh})."
      return
    end

    want = env_or_prompt("SET_DEFAULT_ZSH", "\nChange default login shell to zsh? (#{zsh}) [y/N]: ")
    return unless want

    unless system("chsh", "-s", zsh)
      @errors << "chsh failed. Try manually: chsh -s #{Shellwords.escape(zsh)}"
      return
    end

    puts "Default shell set to zsh (takes effect after next login)."
  end

  def maybe_install_oh_my_zsh
    want = env_or_prompt("INSTALL_OMZ", "\nInstall Oh My Zsh (unattended)? [y/N]: ")
    return unless want

    @omz_selected = true

    if File.directory?(OMZ_DIR)
      puts "Oh My Zsh already present at #{OMZ_DIR}"
      return
    end

    install_oh_my_zsh!
  end

  def write_custom_zshrc_for_omz!
    return unless @omz_selected
    return unless File.directory?(OMZ_DIR)

    path = File.expand_path("~/.zshrc")
    if File.file?(path)
      existing = File.read(path)
      if existing == CUSTOM_OMZ_ZSHRC
        puts "~/.zshrc already matches the bundled Oh My Zsh template; skipping."
        return
      end

      backup = "#{path}.bak.#{Time.now.strftime('%Y%m%d%H%M%S')}"
      File.rename(path, backup)
      puts "Backed up existing ~/.zshrc to #{backup}"
    end

    File.write(path, CUSTOM_OMZ_ZSHRC)
    File.chmod(0o644, path)
    puts "Wrote custom ~/.zshrc for Oh My Zsh."
  rescue StandardError => e
    @errors << "Failed to write ~/.zshrc: #{e.message}"
  end

  def install_oh_my_zsh!
    unless find_executable("curl")
      @errors << "curl missing; cannot fetch Oh My Zsh installer."
      return
    end

    puts "Downloading and running Oh My Zsh installer (unattended)…"
    cmd = [
      "bash", "-c",
      "set -euo pipefail; " \
      "curl -fsSL #{Shellwords.escape(OMZ_INSTALL_URL)} | bash -s -- --unattended"
    ]
    ok, log = capture(cmd)
    unless ok
      warn log.lines.last(20).join if log && !log.empty?
      @errors << "Oh My Zsh install script failed."
      return
    end

    puts "Oh My Zsh installed under #{OMZ_DIR}"
  end

  def finalize_success
    if @errors.any?
      finalize_failure
      return
    end

    puts "\nDone. Log out and back in if you changed the default shell."
    exit 0
  end

  def finalize_failure
    warn "\nFinished with errors:"
    @errors.each { |e| warn "  - #{e}" }
    exit 1
  end

  # --- small utilities ---

  def interactive?
    $stdin.tty? && $stdout.tty?
  end

  def env_or_prompt(env_name, prompt)
    v = ENV[env_name]
    if v
      return truthy?(v)
    end
    return false unless interactive?

    print prompt
    truthy?($stdin.gets.to_s.strip)
  end

  def truthy?(s)
    %w[y yes 1 true on].include?(s.to_s.downcase)
  end

  def zsh_path
    out, = Open3.capture2("which", "zsh")
    path = out.strip
    path.empty? ? nil : path
  end

  def current_shell
    Etc.getpwuid(Process.uid).shell
  rescue StandardError
    nil
  end

  def find_executable(name)
    exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
    ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |dir|
      exts.each do |ext|
        file = File.join(dir, "#{name}#{ext}")
        return file if File.executable?(file) && !File.directory?(file)
      end
    end
    nil
  end

  def capture(argv)
    stdout, stderr, status = Open3.capture3(*argv)
    log = [stdout, stderr].reject(&:empty?).join("\n")
    [status.success?, log]
  end
end

DebianZshSetup.new.run if $PROGRAM_NAME == __FILE__
