#!/bin/bash
#
# timedatectl - control the system time and date
# Bash implementation for systems using timedated without systemd.
#
# rizitis 2026, GPLv2+
#V=2

DBUS_DEST="org.freedesktop.timedate1"
DBUS_PATH="/org/freedesktop/timedate1"
DBUS_IFACE="org.freedesktop.timedate1"

die() {
  echo "timedatectl: $*" >&2
  exit 1
}

need_busctl() {
  command -v busctl &>/dev/null || die "busctl not found"
}

get_property() {
  busctl get-property "$DBUS_DEST" "$DBUS_PATH" "$DBUS_IFACE" "$1" 2>/dev/null | cut -d' ' -f2- | tr -d '"'
}

cmd_status() {
  need_busctl

  local timezone local_rtc can_ntp use_ntp ntp_sync
  local time_now rtc_time

  timezone=$(get_property Timezone)
  local_rtc=$(get_property LocalRTC)
  can_ntp=$(get_property CanNTP)
  use_ntp=$(get_property NTP)
  ntp_sync=$(get_property NTPSynchronized)

  local_rtc_str="no";  [ "$local_rtc" = "true" ] && local_rtc_str="yes"
  can_ntp_str="no";    [ "$can_ntp"   = "true" ] && can_ntp_str="yes"
  use_ntp_str="no";    [ "$use_ntp"   = "true" ] && use_ntp_str="yes"
  ntp_sync_str="no";   [ "$ntp_sync"  = "true" ] && ntp_sync_str="yes"

  time_now=$(date)
  rtc_time=$(hwclock --show 2>/dev/null | head -1 || echo "n/a")

  printf "               Local time: %s\n" "$time_now"
  printf "           Universal time: %s\n" "$(date -u)"
  printf "                 RTC time: %s\n" "$rtc_time"
  printf "                Time zone: %s\n" "$timezone"
  printf "              NTP enabled: %s\n" "$use_ntp_str"
  printf "         NTP synchronized: %s\n" "$ntp_sync_str"
  printf "          RTC in local TZ: %s\n" "$local_rtc_str"
  printf "               CanNTP: %s\n"     "$can_ntp_str"
}

cmd_show() {
  need_busctl

  local timezone local_rtc can_ntp use_ntp ntp_sync
  timezone=$(get_property Timezone)
  local_rtc=$(get_property LocalRTC)
  can_ntp=$(get_property CanNTP)
  use_ntp=$(get_property NTP)
  ntp_sync=$(get_property NTPSynchronized)

  printf "Timezone=%s\n"         "$timezone"
  printf "LocalRTC=%s\n"         "$local_rtc"
  printf "CanNTP=%s\n"           "$can_ntp"
  printf "NTP=%s\n"              "$use_ntp"
  printf "NTPSynchronized=%s\n"  "$ntp_sync"
  printf "TimeUSec=%s\n"         "$(date +%s)000000"
  printf "RTCTimeUSec=%s\n"      "$(hwclock --show 2>/dev/null | awk '{print $1"T"$2}' || echo 'n/a')"
}

cmd_set_timezone() {
  need_busctl
  [ -z "$1" ] && die "set-timezone: missing timezone argument"
  busctl call "$DBUS_DEST" "$DBUS_PATH" "$DBUS_IFACE" SetTimezone sb "$1" false \
    || die "Failed to set timezone"
}

cmd_set_local_rtc() {
  need_busctl
  local val="$1"
  [ -z "$val" ] && die "set-local-rtc: missing argument (0 or 1)"
  case "$val" in
    0|false|no)  val_bool="false" ;;
    1|true|yes)  val_bool="true"  ;;
    *) die "set-local-rtc: invalid argument '$val' (use 0 or 1)" ;;
  esac
  busctl call "$DBUS_DEST" "$DBUS_PATH" "$DBUS_IFACE" SetLocalRTC bbb "$val_bool" false false \
    || die "Failed to set local RTC"
}

cmd_set_ntp() {
  need_busctl
  local val="$1"
  [ -z "$val" ] && die "set-ntp: missing argument (0 or 1)"
  case "$val" in
    0|false|no)  val_bool="false" ;;
    1|true|yes)  val_bool="true"  ;;
    *) die "set-ntp: invalid argument '$val' (use 0 or 1)" ;;
  esac
  busctl call "$DBUS_DEST" "$DBUS_PATH" "$DBUS_IFACE" SetNTP bb "$val_bool" false \
    || die "Failed to set NTP"
}

cmd_set_time() {
  need_busctl
  [ -z "$1" ] && die "set-time: missing argument (e.g. '2026-05-31 20:30:00')"
  local ntp
  ntp=$(get_property NTP)
  [ "$ntp" = "true" ] && die "set-time: NTP is enabled — disable it first with: timedatectl set-ntp 0"

  cat >&2 <<'WARN'

  ╔══════════════════════════════════════════════════════════════╗
  ║                        !! WARNING !!                        ║
  ║                                                             ║
  ║  You are about to SET THE SYSTEM TIME MANUALLY.             ║
  ║  If you enter the wrong time, your system clock will be     ║
  ║  wrong and NTP may take a long time to correct it.          ║
  ║                                                             ║
  ║  To recover from a wrong set-time, run:                     ║
  ║    sudo timedatectl sync-now                                ║
  ║                                                             ║
  ╚══════════════════════════════════════════════════════════════╝

WARN

  printf "  Current time : %s\n" "$(date)" >&2
  printf "  You entered  : %s\n\n" "$1" >&2
  printf "  Are you sure? [yes/N]: " >&2
  read -r confirm
  [ "$confirm" = "yes" ] || die "set-time: aborted"

  local usec
  usec=$(date -d "$1" +%s%6N 2>/dev/null) || die "set-time: invalid time format '$1'"
  busctl call "$DBUS_DEST" "$DBUS_PATH" "$DBUS_IFACE" SetTime xbb "$usec" false false \
    || die "Failed to set time"
}

cmd_list_timezones() {
  need_busctl
  busctl call "$DBUS_DEST" "$DBUS_PATH" "$DBUS_IFACE" ListTimezones \
    | tr ' ' '\n' | grep '/' | tr -d '"' | sort
}

cmd_sync_now() {
  [ "$(id -u)" -ne 0 ] && die "sync-now: must be run as root"

  local ntpd_rc="/etc/rc.d/rc.ntpd"
  local was_running=0

  pidof ntpd &>/dev/null && was_running=1

  # Step 1: stop ntpd so ntpdate can bind to the NTP port
  [ "$was_running" -eq 1 ] && bash "$ntpd_rc" stop &>/dev/null

  # Step 2: immediate one-shot step with ntpdate (reliable, handles large offsets)
  if command -v ntpdate &>/dev/null; then
    ntpdate -b pool.ntp.org &>/dev/null || die "sync-now: ntpdate failed to reach NTP servers"
  else
    # Fallback: ntpd one-shot with timeout so it can't hang
    timeout 30 /usr/sbin/ntpd -qg &>/dev/null \
      || die "sync-now: ntpd one-shot sync failed or timed out"
  fi

  # Step 3: sync the corrected time down to the RTC
  command -v hwclock &>/dev/null && hwclock --systohc &>/dev/null

  # Step 4: restart ntpd only if it was running before (i.e. automatic mode is on)
  if [ "$was_running" -eq 1 ]; then
    bash "$ntpd_rc" start &>/dev/null || die "sync-now: failed to restart ntpd"
  fi

  echo "timedatectl: time synchronized successfully"
}

usage() {
  cat <<EOF
Usage: timedatectl [OPTIONS] COMMAND

Commands:
  status                   Show current time settings
  show                     Show settings in key=value format
  set-timezone ZONE        Set the system timezone (e.g. Europe/Athens)
  set-local-rtc [0|1]      Control whether RTC is in local time
  set-ntp [0|1]            Enable or disable NTP synchronization
  set-time TIME            Set time manually (e.g. '2026-05-31 20:30:00')
  list-timezones           List available timezones
  sync-now                 Force immediate time sync (works even if NTP is off)

Options:
  -h, --help               Show this help
EOF
}

# main
case "$1" in
  ""|status)        cmd_status ;;
  show)             cmd_show ;;
  set-timezone)     cmd_set_timezone "$2" ;;
  set-local-rtc)    cmd_set_local_rtc "$2" ;;
  set-ntp)          cmd_set_ntp "$2" ;;
  set-time)         cmd_set_time "$2" ;;
  list-timezones)   cmd_list_timezones ;;
  sync-now)         cmd_sync_now ;;
  -h|--help)        usage ;;
  *)                die "Unknown command '$1'. Try --help." ;;
esac
