#!/usr/bin/env python3
"""Check whether there is a software update pending on a Starlink user terminal.

Optionally, reboot the dish to initiate install if there is an update pending.
"""

import argparse
from datetime import datetime
import logging
import sys
import time

import grpc

import loop_util
import starlink_grpc

# This is the enum value spacex_api.device.dish_pb2.SoftwareUpdateState.REBOOT_REQUIRED
REBOOT_REQUIRED = 6
# This is the enum value spacex_api.device.dish_pb2.SoftwareUpdateState.DISABLED
UPDATE_DISABLED = 7


def loop_body(opts, context):
    now = time.time()

    try:
        status = starlink_grpc.get_status(context)
    except (AttributeError, ValueError, grpc.RpcError) as e:
        logging.error("Failed getting dish status: %s", str(starlink_grpc.GrpcError(e)))
        return 1

    # There are at least 3 and maybe 4 redundant flags that indicate whether or
    # not a software update is pending. In order to be robust against future
    # changes in the protocol and/or implementation of it, this scripts checks
    # them all, while allowing for the possibility that some of them have been
    # obsoleted and thus no longer present in the reflected protocol classes.

    try:
        alert_flag = status.alerts.install_pending
    except (AttributeError, ValueError):
        alert_flag = None

    try:
        state_flag = status.software_update_state == REBOOT_REQUIRED
        state_dflag = status.software_update_state == UPDATE_DISABLED
    except (AttributeError, ValueError):
        state_flag = None
        state_dflag = None

    try:
        stats_flag = status.software_update_stats.software_update_state == REBOOT_REQUIRED
        stats_dflag = status.software_update_stats.software_update_state == UPDATE_DISABLED
    except (AttributeError, ValueError):
        stats_flag = None
        stats_dflag = None

    try:
        ready_flag = status.swupdate_reboot_ready
    except (AttributeError, ValueError):
        ready_flag = None

    try:
        sw_version = status.device_info.software_version
    except (AttributeError, ValueError):
        sw_version = "UNKNOWN"

    if opts.verbose >= 2:
        print("Pending flags:", alert_flag, state_flag, stats_flag, ready_flag)
        print("Disable flags:", state_dflag, stats_dflag)

    if state_dflag or stats_dflag:
        logging.warning("Software updates appear to be disabled")

    # The swupdate_reboot_ready field does not appear to be in use, so may
    # mean something other than what it sounds like. Only use it if none of
    # the others are available.
    if alert_flag is None and state_flag is None and stats_flag is None:
        install_pending = bool(ready_flag)
    else:
        install_pending = alert_flag or state_flag or stats_flag

    if opts.verbose:
        dtnow = datetime.fromtimestamp(now, tz=getattr(opts, "timezone", None))
        print(dtnow.replace(microsecond=0, tzinfo=None).isoformat(), "- ", end="")

    if install_pending:
        print("Install pending, current version:", sw_version)
        if opts.install:
            print("Rebooting dish to initiate install")
            try:
                starlink_grpc.reboot(context)
            except starlink_grpc.GrpcError as e:
                logging.error("Failed reboot request: %s", str(e))
                return 1
    elif opts.verbose:
        print("No install pending, current version:", sw_version)

    return 0


def parse_args():
    parser = argparse.ArgumentParser(description="Check for Starlink user terminal software update")
    parser.add_argument(
        "-i",
        "--install",
        action="store_true",
        help="Initiate dish reboot to perform install if there is an update pending")
    parser.add_argument("-g",
                        "--target",
                        help="host:port of dish to query, default is the standard IP address "
                        "and port (192.168.100.1:9200)")
    parser.add_argument("-v",
                        "--verbose",
                        action="count",
                        default=0,
                        help="Increase verbosity, may be used multiple times")
    loop_util.add_args(parser)
    opts = parser.parse_args()

    loop_util.check_args(opts, parser)

    return opts


def main():
    opts = parse_args()

    logging.basicConfig(format="%(levelname)s: %(message)s")

    context = starlink_grpc.ChannelContext(target=opts.target)

    try:
        rc = loop_util.run_loop(opts, loop_body, opts, context)
    finally:
        context.close()

    sys.exit(rc)


if __name__ == "__main__":
    main()
