#!/usr/bin/env python3

import sys
import os
import logging
import subprocess


def run_cmd_safe(cmd, expire_cmd=None, timeout=6, retry=10):
    """Safely executes a command with timeout. Logs stdout and stderr. Captures TimeOutException.

    Args:
        cmd (list): Command to be executed
    """
    result = None
    retry_count = 0
    while retry_count < retry:
        if retry_count > 0 and expire_cmd:
            logging.warn(f"Running expire command {expire_cmd}")
            run_cmd_safe(expire_cmd)
        try:
            logging.debug(f"Executing {' '.join(cmd)}")
            result = subprocess.run(cmd, capture_output=True, timeout=timeout)
            logging.info(f"{' '.join(cmd)}: {result.stdout}")
            logging.error(f"{' '.join(cmd)}: {result.stderr}")
            break
        except subprocess.TimeoutExpired:
            logging.error(f"Attempt {retry_count}")
            logging.error(f"Command expired: {cmd}")
            retry_count += 1

    return result


def get_network_state(conn_uuid):
    """Using nmcli, retreive the state of the given network

    Args:
        conn_uuid (str): The connection UUID provided by `nmcli connection show`

    Returns:
        str: The state of the connection provided by nmcli
    """
    UUID = 0
    STATE = 1
    CMD = ['nmcli', '-t', '-f', 'con-uuid,state', 'device', 'status']

    result = run_cmd_safe(CMD)
    decoded_result = result.stdout.decode()
    network_connections = decoded_result.split("\n")
    valid_connections = list(filter(
        lambda item: item[0] != "",
        [conn.split(':') for conn in network_connections]))
    selected_network = list(filter(lambda item: item[UUID] == conn_uuid, valid_connections))
    if len(selected_network) > 0:
        return selected_network[0][STATE]
    else:
        return ''


def network_connected(conn_uuid):
    """Returns True if the given connection UUID is connected

    Args:
        conn_uuid (str): The connection UUID provided by `nmcli connection show`

    Returns:
        bool: True if connected, False otherwise
    """
    CONNECTED = "connected"

    current_state = get_network_state(conn_uuid)
    return current_state == CONNECTED


def one_up(conn_uuids):
    """Returns True if at least one of the provided network connections is up

    Args:
        conn_uuids (list): List of connections to check
    """
    for conn_uuid in conn_uuids:
        if network_connected(conn_uuid):
            return True
    return False


def set_wifi_state(on=True):
    """Turns the wifi on and off

    Args:
        on (bool, optional): Set to False to turn wifi off. Defaults to True.

    Returns:
        bool: True if command successful, False if otherwise
    """
    desired_state = "on" if on else "off"
    cmd = ["nmcli", "radio", "wifi", desired_state]
    result = run_cmd_safe(cmd)
    return result.returncode == 0


def is_mountpoint(path):
    cmd = ["mountpoint", path]
    result = run_cmd_safe(cmd)
    return result.returncode == 0


if __name__ == "__main__":

    logging.basicConfig(
        filename='/var/log/nmd.log',
        encoding='utf-8',
        level=logging.DEBUG,
        format='%(asctime)s %(levelname)s: %(message)s',
        datefmt='%m/%d/%Y %I:%M:%S %p')

    logging.debug("----------Start----------")

    # List of connections relevant to this script
    # Use tags to denote ethernet or wifi
    CONNECTIONS = {
        "home": {
            "029a0daa-9dcd-36c2-9f3f-8c8a4da10da0": {
                "tags": ["ethernet"]
            },
            "991b3332-3b25-467d-b49d-daecb968b4f8": {
                "tags": ["wifi"]
            }
        }
    }

    # List of valid states for NetworkManager
    # Taken from https://developer-old.gnome.org/NetworkManager/unstable/NetworkManager-dispatcher.html
    STATES = {
        "pre-up": "pre-up",
        "up": "up",
        "pre-down": "pre-down",
        "down": "down",
        "vpn-pre-up": "vpn-pre-up",
        "vpn-up": "vpn-up",
        "vpn-pre-down": "vpn-pre-down",
        "vpn-down": "vpn-down",
        "hostname": "hostname",
        "dhcp4-change": "dhcp4-change",
        "dhcp6-change": "dhcp6-change",
        "connectivity-change": "connectivity-change",
    }

    # List of available environment variables given by NetworkManager
    # Taken from https://developer-old.gnome.org/NetworkManager/unstable/NetworkManager-dispatcher.html
    # Note: omits DHCP4_<dhcp-option-name> and IP6_<name> for simplicity's sake
    ENV_VARS = {
        "NM_DISPATCHER_ACTION": "NM_DISPATCHER_ACTION",
        "CONNECTION_UUID": "CONNECTION_UUID",
        "CONNECTION_ID": "CONNECTION_ID",
        "CONNECTION_DBUS_PATH": "CONNECTION_DBUS_PATH",
        "CONNECTION_FILENAME": "CONNECTION_FILENAME",
        "CONNECTION_EXTERNAL": "CONNECTION_EXTERNAL",
        "DEVICE_IFACE": "DEVICE_IFACE",
        "DEVICE_IP_IFACE": "DEVICE_IP_IFACE",
        "IP4_ADDRESS_N": "IP4_ADDRESS_N",
        "IP4_NUM_ADDRESSES": "IP4_NUM_ADDRESSES",
        "IP4_GATEWAY": "IP4_GATEWAY",
        "IP4_ROUTE_N": "IP4_ROUTE_N",
        "IP4_NUM_ROUTES": "IP4_NUM_ROUTES",
        "IP4_NAMESERVERS": "IP4_NAMESERVERS",
        "IP4_DOMAINS": "IP4_DOMAINS",
        "CONNECTIVITY_STATE": "CONNECTIVITY_STATE",
    }

    # Used to retrive values from dictionaries after they've been turned into .items()
    KEY = 0
    VALUE = 1

    # Filter out all home connections
    home_connections = CONNECTIONS.get("home").keys()
    logging.debug(f"Home connections: {home_connections}")

    # Filter out our ethernet connections per their tags and save to a list
    ethernets = list(map(
        lambda conn: conn[KEY],
        filter(
            lambda conn: "ethernet" in conn[VALUE].get("tags") or [],
            CONNECTIONS["home"].items())))
    logging.debug(f"Ethernet connections: {ethernets}")

    # The interface and state are always passed as positional arguments
    logging.debug(f"arguments: {sys.argv}")
    interface, state = sys.argv[1:3]
    logging.debug(f"interface: {interface}")
    logging.debug(f"state: {state}")

    # Get the environment variables from our dictionary above
    environment = {var[KEY]: os.getenv(var[VALUE]) for var in ENV_VARS.items()}
    logging.debug(f"enviroment: {environment}")

    # Get our conn_uuid from the dictionary of environment variables
    conn_uuid = environment.get(ENV_VARS["CONNECTION_UUID"])
    logging.debug(f"Connection UUID: {conn_uuid}")

    # check if we need to turn the wifi on or off
    if conn_uuid in ethernets:
        # If the state of our home ethernet connection is "up" (we've just connected to ethernet),
        # turn wifi off.
        if state == STATES["up"]:
            set_wifi_state(on=False)
        # If the state of our home ethernet connection is "down" (we've just disconnected from
        # ethernet), turn wifi back on.
        elif state == STATES["down"]:
            set_wifi_state(on=True)

    # When we connect to a home network, mount our shares
    # When we disconnect from all home networks, unmount our shares
    # one_home_connection_up = one_up(home_connections)
    # logging.debug(f"One Home Connection Up: {one_home_connection_up}")

    # umount_cmd = ["umount", "-a", "-l", "-t", "cifs"]
    # mount_cmd = ["mount", "/mnt/truenas"]
    # if one_home_connection_up:
    #     run_cmd_safe(mount_cmd, expire_cmd=umount_cmd)
    # else:
    #     run_cmd_safe(umount_cmd)

    # Log Done
    logging.debug("----------Done----------")
