#!/usr/bin/python3
# coding=utf-8

"""
bcdiscover
==========
Breadcrumb scanning tool. Scan the network and discover Rajant breadcrumbs
offering the requested service name.

"""

import ipaddress
import json
import logging
import subprocess
import sys
from string import Template

import bcutils3dp.discovery.constants
import bcutils3dp.discovery.sup

FORMAT = "%i,%p,%s,%v,%b,%w,%l%n"


def _filter_ip4(item):
    ipa = ipaddress.ip_address(item.source)
    return ipa.version == 4


def _filter_ip6_link_local(item):
    ipa = ipaddress.ip_address(item.source)
    return ipa.version == 6 and ipa.is_link_local


def _get_filter_function(cmd_args):
    if cmd_args.ip4:
        return _filter_ip4
    if cmd_args.ip6ll:
        return _filter_ip6_link_local
    return lambda x: True


def _filter_results(items, cmd_args):
    return filter(_get_filter_function(cmd_args), items)


def _view_results(crumb=None, fmt=FORMAT):
    if not crumb:
        return
    template_fmt = fmt.replace("$", "$$").replace("%", "$")
    line = Template(template_fmt)
    line = line.safe_substitute(
        i=crumb.source,
        p=crumb.port,
        s=crumb.serial,
        v=crumb.version,
        b=crumb.build,
        l=1 if crumb.local else 0,
        d=crumb.platform if crumb.platform else "",
        w=crumb.network if crumb.network else "",
        m=crumb.name if crumb.name else "",
        f=crumb.source_in6_flowinfo if crumb.source_in6_flowinfo else "",
        c=crumb.source_in6_scopeid if crumb.source_in6_scopeid else "",
        e="%",
        n="\n",
        t="\t",
    )
    sys.stdout.write(str(line))


if __name__ == "__main__":

    import argparse

    parser = argparse.ArgumentParser(
        description="Scan and find Rajant Breadcrumbs attached to the network"
    )

    parser.add_argument(
        "--debug", action="store_true", help="Enable DEBUG level logging"
    )

    parser.add_argument(
        "-l",
        "--local",
        action="store_true",
        help="Only show locally attached Breadcrumb(s)",
    )

    parser.add_argument(
        "-f",
        "--format",
        default=FORMAT,
        help="""Output format. (default: %(default)s)""",
    )

    parser.add_argument(
        "--fmthelp", action="store_true", help="Show format options and exit"
    )

    parser.add_argument(
        "-t",
        "--scantime",
        type=int,
        default=5000,
        help="How long to scan for. (default: %(default)s)",
    )

    parser.add_argument(
        "-s",
        "--service",
        default=bcutils3dp.discovery.constants.SERVICE_V11,
        help='Service name to scan for. (default: "%(default)s")',
    )

    parser.add_argument(
        "-i",
        "--interface",
        default=None,
        help="""Network Interface to use for discovery. If None is
                        specified, then all available interfaces are used.
                        (default: "%(default)s")""",
    )

    parser.add_argument(
        "--maxhits",
        default=0,
        type=int,
        help="""Stop scanning once you have this many hits. If 0
                        value is ignored. (default: "%(default)s")""",
    )

    filter_group = parser.add_mutually_exclusive_group(required=False)

    filter_group.add_argument(
        "--ip4", help="only list breadcrumbs with ipv4", action="store_true"
    )

    filter_group.add_argument(
        "--ip6ll",
        help="only list breadcrumbs with link local ipv6",
        action="store_true",
    )

    args = parser.parse_args()
    del parser

    if args.fmthelp:
        print(
            """
Output format definition:
--------------------------
%i = the discovered breadcrumb's ip ( ip4 or ip6 )
%p = the port to use to connect to the requested service
%s = the discovered breadcrumb's serial number
%v = the discovered breadcrumb's firmware version
%b = the discovered breadcrumb's build
%w = the discovered breadcrumb's networkname
%m = the discovered breadcrumb's name
%l = 1 if there is a wired connection from the scanner to the breadcrumb
%f = ip6 source flowid
%c = ip6 source scope id
%d = breadcrumb device platform
%e = %
%t = tab
%n = newline"""
        )
        exit(0)

    if args.debug:
        root = logging.getLogger()
        root.setLevel(logging.DEBUG)
        root.addHandler(logging.StreamHandler())
        logging.debug("Debugging is on...")

    if args.scantime < 100 or args.scantime > 3600000:
        raise ValueError("Invalid scantime. Expected [100-3600000]")

    logging.debug("local scan     : %s", bool(args.local))
    logging.debug("scan time      : %s", args.scantime)
    logging.debug("output format  : %s", args.format)
    logging.debug("service name   : %s", args.service)
    logging.debug(
        "provided iface : %s", args.interface if args.interface else "all available"
    )

    INTERFACE_MASTER = None
    if args.interface:
        link_data = subprocess.run(
            ["ip", "-j", "link", "show", args.interface],
            capture_output=True,
            check=False,
        )
        iface_info = json.loads(link_data.stdout)[0]
        INTERFACE_MASTER = iface_info.get("master", args.interface)
    logging.debug(
        "scanning iface : %s", INTERFACE_MASTER if args.interface else "all available"
    )

    sup = bcutils3dp.discovery.sup.SupDiscovery(
        interface=INTERFACE_MASTER,
        service=args.service,
        local=args.local,
        scantime=args.scantime,
        maxhits=args.maxhits,
    )
    results = sup.execute()
    for n in _filter_results(results, args):
        _view_results(n, args.format)
