#!/usr/bin/env python3
"""Sunset Alarm — alert at a specified offset before sunset.

Usage:
    sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]
"""

import argparse
import json
import subprocess
import sys
import time
from datetime import datetime, timedelta
from urllib.error import URLError
from urllib.request import urlopen


def parse_coordinate(value):
    """Parse 'lat:long' string into two floats."""
    parts = value.split(":")
    if len(parts) != 2:
        raise argparse.ArgumentTypeError(
            f"invalid coordinate format '{value}': expected LAT:LONG"
        )
    try:
        lat, lon = float(parts[0]), float(parts[1])
    except ValueError:
        raise argparse.ArgumentTypeError(
            f"invalid coordinate values in '{value}': must be numbers"
        )
    if not -90 <= lat <= 90:
        raise argparse.ArgumentTypeError(f"latitude {lat} out of range [-90, 90]")
    if not -180 <= lon <= 180:
        raise argparse.ArgumentTypeError(f"longitude {lon} out of range [-180, 180]")
    return lat, lon


def parse_hardcoded_time(value):
    """Parse 'HH:MM' string into a time object and validate."""
    try:
        t = datetime.strptime(value, "%H:%M").time()
    except ValueError:
        raise argparse.ArgumentTypeError(
            f"invalid time format '{value}': expected HH24:MI"
        )
    return t


def fetch_sunset(lat, lon):
    """Query Open-Meteo for today's sunrise/sunset and return a datetime.

    Uses the `current日出_sunset` parameter set which returns values in UTC.
    """
    url = (
        f"https://api.open-meteo.com/v1/forecast?"
        f"latitude={lat}&longitude={lon}"
        f"&current=sunrise,sunset"
        f"&timezone=auto"
    )
    with urlopen(url, timeout=30) as resp:
        data = json.loads(resp.read().decode("utf-8"))

    sunrise_utc_str = data["current"]["sunrise"]  # e.g. '2026-10-17T04:23'
    sunset_utc_str = data["current"]["sunset"]    # e.g. '2026-10-17T18:56'

    sunset_dt = datetime.strptime(sunset_utc_str, "%Y-%m-%dT%H:%M")
    return sunset_dt


def notify_send(title, message):
    """Send a desktop notification via notify-send."""
    try:
        subprocess.run(
            ["notify-send", title, message],
            check=False,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
    except FileNotFoundError:
        pass  # notify-send not available; fall through to stdout


def main():
    parser = argparse.ArgumentParser(
        description="Alert at a specified number of minutes before sunset.",
    )
    location_group = parser.add_mutually_exclusive_group(required=True)
    location_group.add_argument(
        "-l",
        type=parse_coordinate,
        metavar="LAT:LONG",
        help="Latitude and longitude (e.g. 32.123:-111.111)",
    )
    location_group.add_argument(
        "-t",
        type=parse_hardcoded_time,
        metavar="HH24:MI",
        help="Hardcoded sunset time (e.g. 18:00)",
    )
    parser.add_argument(
        "-m",
        type=int,
        required=True,
        metavar="MINUTES",
        help=(
            "Minutes offset before/after sunset to trigger the alert. "
            "Positive = before sunset; negative = after."
        ),
    )
    parser.add_argument(
        "-h",
        action="help",
        help="Show this help message and exit.",
    )
    args = parser.parse_args()

    offset_minus = timedelta(minutes=args.m)

    # --- Phase 1: resolve sunset time ---------------------------
    while True:
        try:
            if args.l is not None:
                lat, lon = args.l
                sunset_dt = fetch_sunset(lat, lon)
                print(f"Sunset fetched from Open-Meteo: {sunset_dt.isoformat()}")
                break  # success — stop retry loop
            else:
                now = datetime.now()
                today_date = today_date_naive = now.replace(hour=0, minute=0, second=0, microsecond=0)
                sunset_today = datetime.combine(today_date_naive, args.t)
                print(f"Using hardcoded sunset: {args.t}")
                break  # no network needed for -t
        except (URLError, OSError) as exc:
            print(f"Network unavailable ({exc}), retrying in 5 minutes...", file=sys.stderr)
            time.sleep(300)

    # --- Phase 2: wait / loop and alert -------------------------
    alert_threshold = sunset_dt - offset_minus

    if alert_threshold <= datetime.now():
        # Already past the alert window — fire once and exit.
        notify_send(
            "Sunset Alarm",
            f"Alert triggered: {sunset_dt.strftime('%H:%M')} ({offset_minus.total_seconds()/60:+.0f} min from sunset)",
        )
        print(f"Alert: {alert_threshold.isoformat()} -> Now: {datetime.now().isoformat()}")
        return

    while True:
        now = datetime.now()

        if not (alert_threshold <= now < sunset_dt.replace(year=now.year + 1)):
            # Normal path: only alert when within the window.
            # Check again in 60 seconds.
            time.sleep(60)
            continue

        if now.date() > sunset_dt.date():
            # Day has passed — stop monitoring, wait until tomorrow.
            print(f"Day {sunset_dt.date()} over; re-fetching for new day...", file=sys.stderr)
            # Re-enter fetch loop
            while True:
                try:
                    lat, lon = args.l
                    sunset_dt = fetch_sunset(lat, lon)
                    break
                except (URLError, OSError) as exc:
                    print(f"Network unavailable ({exc}), retrying in 5 minutes...", file=sys.stderr)
                    time.sleep(300)
            continue

        # Within alert window — send notification.
        notify_send(
            "Sunset Alarm",
            f"Alert triggered: {sunset_dt.strftime('%H:%M')} ({offset_minus.total_seconds()/60:+.0f} min from sunset)",
        )
        print(f"ALERT -> {alert_threshold.isoformat()} <= {now.isoformat()} < {sunset_dt.isoformat()}")

        # Keep alerting (don't exit).


if __name__ == "__main__":
    main()
