#!/usr/bin/env python3
#
# Check power status via the PowerPCB interface and perform shutdown if
# required
#
# When safe shutdown is deemed necessary:
#   1. log a message to the systemlog and restart services to force netsync
#   2. halt and power off system with 'systemctl poweroff'

import os, json
import argparse
import sys
import time
from datetime import datetime
import logging
from logging.handlers import RotatingFileHandler

for _p in (
    os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib'),
    '/usr/lib/signalytic_bm',
):
    if os.path.isfile(os.path.join(_p, 'serial_console.py')):
        sys.path.insert(0, _p)
        break
from serial_console import open_console, BusyError

# command line
parser = argparse.ArgumentParser()
parser.add_argument("-B", "--boot", help="Calling while booting, means we are recovering from power down", action="store_true")
parser.add_argument("-d", "--dryrun", help="Print changes only, do not apply (implies -v)", action="store_true")
parser.add_argument("-D", "--debug", help="Log additional info", action="store_true")
parser.add_argument("-v", "--verbose", help="Print status information to console", action="store_true")
parser.add_argument("-c", "--stdout", help="Print to stdout only, do not write to blockgraph", action="store_true")
args = parser.parse_args()

if args.dryrun:
    print('Dry run mode active')
    args.stdout = True
    args.verbose = True

# Send a command to running power manager
def send_command(s, command):
    command = command.strip()
    cmd = command+"\r"
    s.reset_output_buffer()
    s.reset_input_buffer()
    sz = s.write(cmd.encode())
    s.flush()
    for i in range(10):
        reply = s.readline().decode().strip()
        if reply and reply!=command: break
    return reply

def init_logging():
  formatter = logging.Formatter("%(asctime)s - %(module)s - %(levelname)s - %(message)s")
  if args.debug:
    log_level = logging.DEBUG
  else:
    log_level = logging.INFO

  # get/reset default logger
  logger = logging.getLogger()
  logger.handlers.clear()
  logger.setLevel(log_level)

  # if verbose, log to stdout
  if args.verbose:
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(log_level)
    console_handler.setFormatter(formatter)
    logger.addHandler(console_handler)

  # always log to file
  log_path = '/var/signalytic/log/power-management'
  os.makedirs(os.path.dirname(log_path), exist_ok=True)
  file_handler = RotatingFileHandler(filename=log_path, maxBytes=1048576, backupCount=3)
  file_handler.setLevel(log_level)
  file_handler.setFormatter(formatter)
  logger.addHandler(file_handler)

# Log to stdout or blockgraph
def writelog(level, system, msg):
    if args.stdout:
        print(f'{level} {system} {msg}')
    else:
        os.system(f'bgsystemlog -l {level} -s {system}  -m "{msg}"')

def exit_on_error(msg):
    logging.error(msg)
    writelog('ERROR', 'POWER', f'safe_shutdown: {msg}')
    sys.exit(1)

# initialize logging module
init_logging()

# Open serial connection (skip silently if another process holds the console;
# the systemd timer will retry on the next tick).
try:
    with open_console(contention='fail', timeout=4) as ser:
        logging.debug('Safe shutdown script starting...')
        logging.debug(f'  port: {ser.port}')
        logging.debug(f'  write to blockgraph: {not args.stdout}')
        logging.debug(f'  dry run: {args.dryrun}')
        logging.debug(f'  boot: {args.boot}')

        # Connectivity check, clear buffer
        ser.reset_output_buffer()
        for i in range(10):
            reply = send_command(ser, "?")
            if reply=="OK": break
        if reply!="OK":
            exit_on_error(f'init expected OK, received: {reply}')

        reply = send_command(ser, "bmstatus -S")
        shdn_state = reply.split(',')
        if len(shdn_state) != 2:
            exit_on_error(f'shutdown status reply format error: {reply}')

        state = int(shdn_state[0])
        seconds_remaining = int(shdn_state[1])
        if state == 2:
            if seconds_remaining%3600 < 90:
                writelog('WARNING', 'POWER', f'estimated {round(seconds_remaining/60)} minutes remaining')
        elif state == 3:
            writelog('WARNING', 'POWER', f'Shutting down ({seconds_remaining} seconds remaining)')
            logging.info(f'Shutting down ({seconds_remaining} seconds remaining)')
            time.sleep(5)
            os.system('shutdown now')
except BusyError:
    sys.exit(0)
except FileNotFoundError as e:
    exit_on_error(str(e))
