#!/bin/sh

# sunset-alarm -- Alert before or after sunset.
# Displays an alert when current time is within (sunset - specified minutes).

###############################################################################
# Defaults
###############################################################################
declare LATLONG="" SUNSET_TIME="" MINUTES="" LAST_DATE="" PERSIST="" NOTIFIED=""
typeset -i SUNSET_EPOCH=0 CURRENT_TIME=0

###############################################################################
# Usage
###############################################################################
usage() {
    cat <<EOF
Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-p] [-h]

Display a notification when a certain time related to sunset has been reached.

Parameters:
    -l) Takes a LAT:LONG parameter to check Open Meteo for the sunset time if
          there is an Internet connection. Otherwise may fail to notify.
    -t) Takes a hard-coded 24-hour formatted time for guessing when sunset is.
    -m) Takes the number of minutes that the alert should appear before sunset.
          Using a negative time is allowed if you prefer having the alert after.
    -p) Persist the notification, showing back up every 5 minutes to midnight.
    -h) Display this usage.

Example: Show the alert 90 minutes before sunset in Colorado City AZ.
    sunset-alarm -l 37:-113 -m 90

Example: Show the alert at exactly 8:30 PM.
    sunset-alarm -t 20:30 -m 0

Example: Show the alert 30 minutes after sunset in Flagstaff AZ.
    sunset-alarm -l 35.1987671:-111.651813 -m -30
EOF
    exit 1
}

###############################################################################
# Parse arguments (handles negative values for -m by using "$@")
###############################################################################
while [ $# -gt 0 ]; do
    case "$1" in
        -l) LATLONG="$2"; shift 2 ;;
        -t) SUNSET_TIME="$2"; shift 2 ;;
        -m) MINUTES="$2"; shift 2 ;;
        -p) PERSIST="true"; shift 1 ;;
        -h) usage ;;
        --) shift; break ;;
        *) echo "ERROR: unknown argument '$1'" >&2; usage ;;
    esac
done

# Validate inputs.
if [ -z "$MINUTES" ]; then
    echo "ERROR: -m MINUTES is required." >&2
    usage
fi

if [ -z "$LATLONG" ] && [ -z "$SUNSET_TIME" ]; then
    echo "ERROR: must specify either -l LAT:LONG or -t HH:MI." >&2
    usage
fi

LATLONG=${LATLONG//\,/:}
LATLONG=${LATLONG//\ /:}
LATLONG=${LATLONG//\_/:}
LATLONG=${LATLONG//\&/:}
LATLONG=${LATLONG//\?/:}
if [[ -n "$LATLONG" && "$LATLONG" != *":"* ]]; then
    echo "ERROR: LAT:LONG is missing the colon." >&2
    usage
fi

###############################################################################
# Internet connectivity check
###############################################################################
check_internet() {
    if curl -s --max-time 5 -o /dev/null https://hyperling.com; then
        echo "Confirmed connectivity with Hyperling."
        return 0
    fi
    if curl -s --max-time 5 -o /dev/null https://mullvad.net; then
        echo "Confirmed connectivity with Mullvad."
        return 0
    fi
    echo "ERROR: Connectivity checks failed." >&2
    return 1
}

###############################################################################
# Get sunset from Open Meteo API
###############################################################################
fetch_sunset_time() {
    echo "Checking for sunset time of lat:long ${LATLONG}."
    LAT=$(echo "$LATLONG" | cut -d: -f1)
    LON=$(echo "$LATLONG" | cut -d: -f2)
    TODAY="`date +'%Y-%m-%d'`"

    #REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&date=today"
    #REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&forecast_days=1"
    REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&start_date=${TODAY}&end_date=${TODAY}"
    RESPONSE=$(curl -s "$REQUEST")
    status="$?"
    echo "Curl command completed with a status of $status."

    if [[ -z "$RESPONSE" ]]; then
        echo "ERROR: Did not receive response. $REQUEST" >&2
        return 1
    fi

    # Extract sunset timestamp like "2025-06-15T19:32:00".
    SUNSET_RAW=$(echo "$RESPONSE" | jq -r '.daily.sunset[0]')

    if [[ -z "$SUNSET_RAW" ]]; then
        echo "ERROR: Could not parse time. $RESPONSE" >&2
        return 1
    fi

    # Convert to epoch.
    SUNSET_EPOCH=$(date -d "$(echo "$SUNSET_RAW" | cut -dT -f1) $(echo "$SUNSET_RAW" | cut -dT -f2)" +%s 2>/dev/null)

    if [[ -z "$SUNSET_EPOCH" ]]; then
        echo "ERROR: Could not calculate epoch. $SUNSET_RAW" >&2
        return 1
    fi

    echo "Got ${SUNSET_RAW} and converted it to epoch ${SUNSET_EPOCH}."
    return 0
}

###############################################################################
# Show alert via fallback chain: fyi -> notify-send -> wall
###############################################################################
show_alert() {
    ALERT=$(date -d @${ALERT_WINDOW_START} +"%H:%M")
    SUNSET=$(date -d @${SUNSET_EPOCH} +"%H:%M")
    PLURAL=$(if (( $MINUTES != 1 )); then echo "s"; fi)

    TITLE="Sunset Alarm ($ALERT)"
    MESSAGE="It's ${MINUTES} minute${PLURAL} to $SUNSET."

    keep_alive="-t 0 --wait"
    icon="-i weather-clear -u critical"

    if command -v fyi >/dev/null 2>&1; then
        fyi $icon $keep_alive "$TITLE" "$MESSAGE"
    elif command -v notify-send >/dev/null 2>&1; then
        notify-send $icon $keep_alive "$TITLE" "$MESSAGE"
    elif command -v wall >/dev/null 2>&1; then
        echo "${TITLE}: ${MESSAGE}" | wall
    else
        echo "ERROR: No notification tool found." >&2
        echo "Install one of: fyi, notify-send (libnotify-bin), wall (bsd-write)" >&2
        return 1
    fi
}

###############################################################################
# Sleep until a certain amount of time
###############################################################################
rest() {
    if [[ "$CURRENT_TIME" -ge 0 ]]; then
        TARGET="$1"
        sleep_seconds=$(( TARGET - CURRENT_TIME ))
        echo "Sleeping ${sleep_seconds}s until $(date -d @$TARGET +%H:%M:%S)."
        sleep "$sleep_seconds"
    fi
}

###############################################################################
# Main loop
###############################################################################
while true; do
    TODAY=$(date +%Y-%m-%d)
    CURRENT_TIME=$(date +%s)

    # Check if we need to fetch new sunset time for a new day.
    if [[ "$LAST_DATE" != "$TODAY" || "$SUNSET_EPOCH" == 0 ]]
    then
        LAST_DATE="$TODAY"

        if [[ -n "$LATLONG" ]]; then
            # Need internet to get sunset time.
            if ! check_internet; then
                echo "ERROR: No internet connection. Retrying in 5 minutes." >&2
                sleep 300
                continue
            fi

            fetch_sunset_time || {
                echo "ERROR: Failed fetching sunset time. Retrying in 5 minutes." >&2
                sleep 300
                continue
            }

            if [[ "$SUNSET_EPOCH" == 0 ]]; then
                echo "ERROR: Sunset time is still null. Retrying in 5 minutes." >&2
                sleep 300
                continue
            fi
        else
            # User provided sunset time directly, convert to epoch.
            SUNSET_EPOCH=$(date -d "${TODAY} ${SUNSET_TIME}" +%s 2>/dev/null)

            if [[ -z "$SUNSET_EPOCH" ]]; then
                echo "ERROR: Could not parse time '${SUNSET_TIME}'." >&2
                sleep 60
                continue
            fi
        fi
    fi

    # Calculate alert window start (sunset minus specified minutes).
    ALERT_WINDOW_START=$(( SUNSET_EPOCH - (MINUTES * 60) ))

    # Calculate midnight of the current day
    MIDNIGHT=$(date -d "${TODAY} +1 day" +%s)

    # Alert while current time is within the window (from window start until midnight).
    if [[ "$SUNSET_EPOCH" -ge 0 \
        && ( -z "$NOTIFIED" || -n "$PERSIST" ) \
        && "$CURRENT_TIME" -ge "$ALERT_WINDOW_START" \
        && "$CURRENT_TIME" -lt "$MIDNIGHT" ]]
    then
        echo "Alert time has been reached, show message."
        show_alert
        NOTIFIED="true"

        echo "Alert has been dismissed, sleeping for 5 minutes."
        sleep 290
        echo "Waking back up from notification dismissal."
    elif [[ "$SUNSET_EPOCH" == 0 ]]; then
        echo "ERROR: Sunset time has not been calculated yet, cannot alert." >&2
    elif [[ -n "$NOTIFIED" && -z "$PERSIST" ]]; then
        echo "Already notified for today ($TODAY)."
        rest $MIDNIGHT
    else
        echo "Not time to notify yet."
        rest $ALERT_WINDOW_START
    fi

    # Reset when we've passed midnight so we fetch the new time for the day.
    if [[ "$CURRENT_TIME" -ge "$MIDNIGHT" ]]; then
        LAST_DATE=""
        NOTIFIED=""
    fi

    sleep 10
done
