import argparse
import json
import logging
import os
import sys
from vstorage.roles_cfg.enums import RESERVED_ROLES
from vstorage.roles_cfg.roles_storage import RolesStorage, RolesCreator, RolesUpgrade


class ArgumentException(Exception):
    pass

class ArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        logger.error(message)
        self.print_usage(sys.stdout)
        self.exit(1)

def check_interface(iface):
    if not iface:
        raise ArgumentException('Argument --interface is required')

usage_template = """
{} command [-i,--interface DEV, -r,--roles ROLE,...]

Roles configuration tool for VStorage

Commands:
  set                apply new set of roles
  append             append roles to existing set
  sync               sync roles database and iptables
  delete             remove roles from existing set
  find               find devices with specific roles
  get                get roles of specific adapter
  upgrade            upgrade roles format
  backup             save roles.json and iptables rules to the file
  restore            restore roles.json and iptables rules

Options:
  -i, --interface DEV              name of network interface to configure
  -r, --roles ROLE,ROLE:PORT,...   network roles for interface delimited by comma
              ROLE                 for predefined roles
              ROLE:PORT            for custom roles
  Predefined roles: {}
"""

def main():
    roles = []
    os.putenv('LANG', 'C')
    roles_creator = RolesCreator(logger)
    usage = usage_template.format(os.path.basename(__file__),
        [r for r in roles_creator.get_predefined_role_names() if r not in RESERVED_ROLES])
    parser = ArgumentParser(usage=usage, add_help=False)
    parser.add_argument('command', type=str,
                        choices=['set',
                                 'append',
                                 'sync',
                                 'delete',
                                 'find',
                                 'get',
                                 'upgrade',
                                 'backup',
                                 'restore'])
    parser.add_argument('-i', '--interface', dest='interface',
                        type=str, required=False)
    parser.add_argument('-r', '--roles', dest='roles',
                        type=str, required=False)
    parser.add_argument('-d', '--debug', dest='debug',
                        action='store_true', required=False)
    parser.add_argument('-o', '--offline', dest='offline',
                        action='store_true', required=False)

    args = parser.parse_args()
    if args.debug:
        logger.setLevel(logging.DEBUG)

    if args.roles:
        for r in args.roles.split(','):
            role_list = r.split(':', 1)
            name = role_list[0]
            port = role_list[1] if (len(role_list) > 1 and role_list[1] != '') else None

            if name in [r.name for r in roles]:
                logger.error('Role name "{}" is already defined'.format(name))
                sys.exit(1)

            if name in RESERVED_ROLES:
                logger.error('Role name "{}" is reserved'.format(name))
                sys.exit(1)

            if (name in roles_creator.get_predefined_role_names() and
                port is not None):
                    logger.error('Cannot set rule for predefined role "{}"'.format(name))
                    sys.exit(1)

            role = roles_creator.create_role(name)
            if port is not None:
                role.add_rule(proto='tcp', dport=port)
                role.add_rule(proto='udp', dport=port)

            roles.append(role)

    roles_storage = RolesStorage(logger, args.offline)
    try:
        if args.command == 'set':
            check_interface(args.interface)
            roles_storage.set(args.interface, roles)
        elif args.command == 'append':
            check_interface(args.interface)
            roles_storage.append(args.interface, roles)
        elif args.command == 'sync':
            roles_storage.sync(args.interface)
        elif args.command == 'delete':
            check_interface(args.interface)
            roles_storage.remove(args.interface, roles)
        elif args.command == 'find':
            out = {}
            out['result'] = roles_storage.find_devices_by_roles(roles)
            sys.stdout.write(json.dumps(out))
        elif args.command == 'get':
            check_interface(args.interface)
            roles = roles_storage.get(args.interface)
            out_roles = []
            for r in roles:
                old_role = {'value': r.name}
                port = None
                if r.name not in roles_creator.get_predefined_role_names():
                    for r in r.rules:
                        if r['dport']:
                            port = int(r['dport'])
                            break
                old_role['port'] = port
                out_roles.append(old_role)
            out = {}
            out['result'] = out_roles
            sys.stdout.write(json.dumps(out))
        elif args.command == 'upgrade':
            RolesUpgrade(logger).upgrade()
        elif args.command == 'backup':
            roles_storage.backup()
        elif args.command == 'restore':
            roles_storage.restore()
        else:
            raise ArgumentException('Unknown command: {}'.format(args.command))

    except ArgumentException as err:
        logger.error('{}'.format(err))
        sys.exit(1)

    except Exception as err:
        logger.error(str(err), exc_info=True)
        sys.exit(1)

    sys.exit(0)

if __name__ == '__main__':
    logger = logging.getLogger("RolesCfgLogger")
    h = logging.StreamHandler(sys.stdout)
    h.setFormatter(logging.Formatter('%(asctime)s.%(msecs)03d %(levelname)s [roles_cfg] %(message)s',
                                     datefmt='%Y-%m-%d %H:%M:%S'))
    logger.addHandler(h)
    logger.setLevel(logging.INFO)
    main()
