#!/usr/bin/python3

import argparse
import logging
import ipaddress
import time
import os
import subprocess
import json
import sys
import re
import jsonrpclib
import ssl

try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    # Legacy Python that doesn't verify HTTPS certificates by default
    pass
else:
    # Handle target environment that doesn't support HTTPS verification
    ssl._create_default_https_context = _create_unverified_https_context

parser = argparse.ArgumentParser(description="Goliath ACL Generator", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-d", "--dry", action='store_true', help="Makes the script return the ACL as a list instead of executing it.")

DRY=False

if len(vars(parser.parse_args())) != 0:
    args = vars(parser.parse_args())

    if args['dry'] is not None and args['dry'] is True:
        DRY=True

logger = logging.getLogger("Gol-ACL-Gen")

def process_input():
    input_lines = sys.stdin.readlines()

    hosts = []
    excludes = []
    includes = []

    for line in input_lines:
        op, address, mask = line.strip().split(',')

        if op == 'ADD':
            includes = includes + [ ipaddress.IPv4Network(f'''{address}/{mask}''') ]
        else:
            excludes = excludes + [ ipaddress.IPv4Network(f'''{address}/{mask}''') ]
    
    return list(dict.fromkeys(ipaddress.collapse_addresses(includes))), list(dict.fromkeys(ipaddress.collapse_addresses(excludes)))

def process_diff(includes, excludes):
    # We need to actually keep collapsing until there is no changes, which is... gross
    diffed = []
    diffed_last = 0

    if len(excludes) == 0:
        return includes
    
    # Do the first pass
    for include in includes:
        for exclude in excludes:
            if exclude.subnet_of(include):
                diffed = diffed + list(include.address_exclude(exclude))
            else:
                diffed = diffed + [ include ]

    while len(diffed) != diffed_last:
        for include in includes:
            for exclude in excludes:
                if exclude.subnet_of(include):
                    diffed = diffed + list(include.address_exclude(exclude))
                else:
                    diffed = diffed + [ include ]
        
        diffed_last = len(diffed)

    return list(dict.fromkeys(ipaddress.collapse_addresses(diffed)))

def build_acl_command(prefixes):
    cmd = [
        "enable",
        "configure session",
        "no ip access-list creatia-acl",
        "ip access-list creatia-acl",
        "counters per-entry"
    ]

    for prefix in prefixes:
        cmd = cmd + [ f"permit ip any {prefix.with_prefixlen}" ]

    return cmd + ["commit"]

def execute_command(command):
    url = "{protocol}://{user}:{pw}@{hostname}/command-api".format(protocol="https", user="creatia", pw="thisisapasswordfortoday", hostname="10.50.28.2")
    server = jsonrpclib.Server(url)

    server.runCmds(1, command)

def main():
    includes, excludes = process_input()

    prefixes = process_diff(includes, excludes)

    command = build_acl_command(prefixes)

    if DRY is True:
        for line in command:
            print(line)
    else:
        execute_command(command)

main()