#!/usr/bin/python3
import argparse
import sys
import enum
import errno

from ixhardware.dmi import parse_dmi
from truenas_api_client import Client, ClientException
from middlewared.plugins.failover_.enums import DisabledReasonsEnum

BASE_NODE = 'Node status: '
BASE_FAILOVER = 'Failover status: '
HEALTHY = 'Failover is healthy.'
UNKNOWN = 'UNKNOWN'


class StatusEnum(enum.Enum):
    NOT_HA = BASE_NODE + 'Not an HA node'
    MASTER = BASE_NODE + 'Active'
    BACKUP = BASE_NODE + 'Standby'
    ELECTING = BASE_NODE + 'Electing active node'
    IMPORTING = BASE_NODE + 'Becoming active node'
    ERROR = BASE_NODE + 'Faulted'
    UNKNOWN = BASE_NODE + 'Unknown'


def get_client():
    try:
        return Client(private_methods=True)
    except Exception as e:
        print_msg_and_exit(f'Unexpected failure enumerating websocket client: {e}')


def get_failover_info(client):
    is_ha = client.call('failover.licensed')
    failover = client.call('failover.config')
    return is_ha, failover


def get_failover_status(client):
    return client.call('failover.status')


def print_msg_and_exit(msg, exit_code=1):
    if msg:
        print(msg)
    sys.exit(exit_code)


def handle_status_command(client, status):
    # print failover status
    failover_status = getattr(StatusEnum, status, StatusEnum.UNKNOWN).value
    print(failover_status)

    print(f'This node serial: {parse_dmi().system_serial_number}')
    timeout = 2
    connect_timeout = 2.0
    options = {'timeout': timeout, 'connect_timeout': connect_timeout}
    try:
        remote_serial = client.call('failover.call_remote', 'system.info', [], options)
        remote_serial = remote_serial['system_serial']
    except Exception as e:
        remote_serial = 'UNKNOWN'
        if isinstance(e, ClientException):
            if e.errno in (errno.ECONNREFUSED, errno.EHOSTUNREACH):
                remote_serial = f'Failed to connect to remote node after {connect_timeout} seconds.'
            elif e.errno == errno.EFAULT and 'Call timeout' in str(e):
                remote_serial = f'Timed out after {timeout} seconds waiting on response from remote node.'

        if remote_serial == 'UNKNOWN':
            remote_serial = f'{e}'

    print(f'Other node serial: {remote_serial}')

    # print failover disabled reason(s) (if any)
    reasons = client.call('failover.disabled.reasons')
    if not reasons:
        print(BASE_FAILOVER + HEALTHY)
    elif len(reasons) == 1:
        try:
            reason = DisabledReasonsEnum[reasons[0]].value
        except KeyError:
            reason = UNKNOWN

        print(BASE_FAILOVER + reason)
    else:
        print(BASE_FAILOVER)
        for idx, reason in enumerate(reasons, start=1):
            try:
                reason = DisabledReasonsEnum[reason].value
            except KeyError:
                reason = UNKNOWN

            print(f'    {idx}: {reason}')

    # end this section with a newline
    print()


def handle_enable_or_disable_command(command, client, status, config):
    if not config['disabled'] and command == 'enable':
        print_msg_and_exit('Failover already enabled.')
    elif config['disabled'] and command == 'disable':
        print_msg_and_exit('Failover already disabled.')
    elif status != 'MASTER':
        print_msg_and_exit('This command can only be run on the Active node.')
    else:
        disabled = command == 'disable'
        try:
            client.call('failover.update', {'disabled': disabled})
        except Exception as e:
            print_msg_and_exit(f'Unexpected failure enabling HA: {e}.')
        else:
            print_msg_and_exit(f'Failover {command}d.', exit_code=0)


def main(command, quiet):
    client = get_client()
    is_ha, failover_config = get_failover_info(client)
    if not is_ha:
        # not an HA system so no reason to continue
        print_msg_and_exit(StatusEnum.NOT_HA.value if not quiet else '')

    failover_status = get_failover_status(client)
    if command == 'status':
        handle_status_command(client, failover_status)
    elif command in ('enable', 'disable'):
        handle_enable_or_disable_command(command, client, failover_status, failover_config)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='HA CLI control utility.')
    parser.add_argument(
        'command',
        default='status',
        nargs='?',
        help=('subcommand: enable disable status'),
        choices=['enable', 'disable', 'status']
    )
    parser.add_argument('-q', help='Be silent if this is a non HA node', action='store_true')
    args = parser.parse_args()
    main(args.command, args.q)
