#!/usr/bin/python3
# -*- coding: utf-8 -*-

from juju_api import JujuApi
from juju.model import ModelObserver
import logging
import os
import os.path
import re


class VCAMonitor(ModelObserver):
    """Monitor state changes within the Juju Model."""
    context = None

    async def on_change(self, delta, old, new, model):
        """React to changes in the Juju model."""
        status = None
        db_nsr = self.context['db_nsr']
        vnf_id = self.context['vnf_id']

        nsr_lcm = db_nsr["_admin"]["deploy"]
        nsr_id = nsr_lcm["id"]
        application = self.context['application']

        if delta.entity == "unit":
            # We only care about changes to a unit
            if delta.type == "add" and old is None:
                if new and new.application == application:
                    status = "BUILD"
            elif delta.type == "change":
                if new and new.application == application:
                    if new.agent_status == "idle":
                        if new.workload_status in ("active", "blocked"):
                            status = "ACTIVE"

            elif delta.type == "remove" and new is None:
                if new and new.application == application:
                    status = "DELETING"

        if status:
            nsr_lcm["VCA"][vnf_id]['operational-status'] = status

            # TODO: Clean this up, and make it work with deletes (if we need
            # TODO: to update the database post-delete)
            # Figure out if we're finished configuring
            count = len(nsr_lcm["VCA"])
            active = 0
            for vnf_id in nsr_lcm["VCA"]:
                if nsr_lcm["VCA"][vnf_id]['operational-status'] == "ACTIVE":
                    active += 1
            if active == count:
                db_nsr["config-status"] = "done"
            else:
                db_nsr["config-status"] = "configuring {}/{}".format(active, count)

            try:
                self.context['db'].replace(
                    "nsrs",
                    nsr_id,
                    db_nsr
                )

                # self.context['db'].replace(
                #     "nsr_lcm",
                #     {"id": self.context['nsr_lcm']['id']},
                #     self.context['nsr_lcm']
                # )
            except Exception as e:
                # I've seen this happen when we handle a delete, because the
                # db record is gone by the time we've finished deleting
                # the charms.
                print("Error updating database: ", e)

    pass


def GetJujuApi(config):
    # Quiet logging from the websocket library. If you want to see
    # everything sent over the wire, set this to DEBUG.
    logging.basicConfig(level=logging.DEBUG)

    ws_logger = logging.getLogger('websockets.protocol')
    ws_logger.setLevel(logging.INFO)

    api = JujuApi(server=config['host'],
                  port=config['port'],
                  user=config['user'],
                  secret=config['secret'],
                  log=ws_logger,
                  model_name='default'
                  )
    return api


def get_vnf_unique_name(nsr_name, vnfr_name, member_vnf_index):
    """Get the unique VNF name.
    Charm names accepts only a to z and non-consecutive - characters."""
    name = "{}-{}-{}".format(nsr_name, vnfr_name, member_vnf_index)
    new_name = ''
    for c in name:
        if c.isdigit():
            c = chr(97 + int(c))
        elif not c.isalpha():
            c = "-"
        new_name += c
    return re.sub('\-+', '-', new_name.lower())


def get_initial_config(initial_config_primitive, mgmt_ip):
    config = {}
    for primitive in initial_config_primitive:
        if primitive['name'] == 'config':
            for parameter in primitive['parameter']:
                param = parameter['name']
                if parameter['value'] == "<rw_mgmt_ip>":
                    config[param] = mgmt_ip
                else:
                    config[param] = parameter['value']
    return config


async def DeployApplication(vcaconfig, db, db_nsr, vnfd,
                            vnfd_index, charm_path):
    """
    Deploy a charm.

    Deploy a VNF configuration charm from a local directory.
    :param dict vcaconfig: The VCA portion of the LCM Configuration
    :param object vnfd: The VNF descriptor
    ...
    :param int vnfd_index: The index of the vnf.

    :Example:

    DeployApplication(...)
    """
    nsr_lcm = db_nsr["_admin"]["deploy"]
    nsr_id = nsr_lcm["id"]
    vnf_id = vnfd['id']

    if "proxy" in vnfd["vnf-configuration"]["juju"]:
        use_proxy = vnfd["vnf-configuration"]["juju"]["proxy"]
    else:
        # TBD: We need this to handle a full charm
        use_proxy = True

    application = get_vnf_unique_name(
        db_nsr["name"].lower().strip(),
        vnfd['id'],
        vnfd_index,
    )

    api = GetJujuApi(vcaconfig)

    await api.login()
    if api.authenticated:
        charm = os.path.basename(charm_path)

        # Set the INIT state; further operational status updates
        # will be made by the VCAMonitor
        nsr_lcm["VCA"][vnf_id] = {}
        nsr_lcm["VCA"][vnf_id]['operational-status'] = 'INIT'
        nsr_lcm["VCA"][vnf_id]['application'] = application

        db.replace("nsrs", nsr_id, db_nsr)

        model = await api.get_model()
        context = {
            'application': application,
            'vnf_id': vnf_id,
            'db_nsr': db_nsr,
            'db': db,
        }
        mon = VCAMonitor()
        mon.context = context
        model.add_observer(mon)

        await api.deploy_application(charm,
                                     name=application,
                                     path=charm_path,
                                     )

        # Get and apply the initial config primitive
        cfg = get_initial_config(
            vnfd["vnf-configuration"].get(
                "initial-config-primitive"
            ),
            nsr_lcm['nsr_ip'][vnfd_index]
        )

        await api.apply_config(cfg, application)

    await api.logout()


async def RemoveApplication(vcaconfig, db, db_nsr, vnfd, vnfd_index):
    """
    Remove an application from the Juju Controller

    Removed the named application and it's charm from the Juju controller.

    :param object loop: The event loop.
    :param str application_name: The unique name of the application.

    :Example:

    RemoveApplication(loop, "ping_vnf")
    RemoveApplication(loop, "pong_vnf")
    """
    nsr_lcm = db_nsr["_admin"]["deploy"]
    vnf_id = vnfd['id']
    application = nsr_lcm["VCA"][vnf_id]['application']

    api = GetJujuApi(vcaconfig)

    await api.login()
    if api.authenticated:
        model = await api.get_model()
        context = {
            'application': application,
            'vnf_id': vnf_id,
            'db_nsr': db_nsr,
            'db': db,
        }

        mon = VCAMonitor()
        mon.context = context
        model.add_observer(mon)

        print("VCA: Removing application {}".format(application))
        await api.remove_application(application)
    await api.logout()
