#!/usr/bin/python3

import MySQLdb
import MySQLdb.cursors
from pprint import pprint
from time import time
import argparse


desc = """
This script does the following:
 1) check if a subscriber, profile, contract or domain
    has both assigned the ncos and the adm_ncos (or the
    ncos_set and adm_ncos_set) and report them as possibile
    conflicts.
 2) when run with the '--apply' option the script will
    migrate the ncos and ncos_set preferences of subscriber,
    profile, contract or domain to adm_ncos and adm_ncos_set.
    In case of conflicts, reported in (1) the preference
    associated to the existing adm_ncos and adm_ncos_set are
    deleted to avoid duplicated preferences.
"""

db_host = "localhost"
db_port = 3306
database = "provisioning"

db_conn = {}

args = None


def logit(str, level, end):
    print("%s-> %s" % (" " * 4 * level, str), end=end)


def log(str, level=0, end="\n"):
    logit(str, level, end)


def error(str, level=0, end="\n"):
    logit("Error: " + str, level, end)


def connect_db():
    try:
        global db_conn
        db_conn = MySQLdb.connect(
            host=db_host,
            port=db_port,
            read_default_file="/etc/mysql/sipwise_extra.cnf",
            database=database,
        )
        log("connected to %s:%s as 'sipwise'" % (db_host, db_port))
        db_conn.autocommit(False)
        log("autocommit=false")
        return db_conn
    except Exception as e:
        error(
            "could not connect to %s:%s as 'sipwise': %s"
            % (db_host, db_port, e)
        )
        return 0


def migrate_ncos_query(voip_usr_preferences):
    return (
        f"UPDATE {voip_usr_preferences} "
        f"SET attribute_id = %s "
        f"WHERE attribute_id = %s;"
    )


def get_attribute_id(attribute):
    db_conn.query(
        f"SELECT id "
        f"FROM provisioning.voip_preferences "
        f"WHERE attribute = '{attribute}';"
    )
    result = db_conn.store_result()
    value = result.fetch_row()
    return value[0][0]


# LEVEL Queries
def fetch_sub_ncos_conflicts_query():
    return f"""
SELECT sub.id, sub.username, nl1.level, nl2.level, up2.id
  FROM voip_subscribers sub
  JOIN voip_usr_preferences up1 ON
    up1.attribute_id = {ncos_id} AND
    up1.subscriber_id = sub.id
  JOIN billing.ncos_levels nl1 ON
    nl1.id = up1.value
  JOIN voip_usr_preferences up2 ON
    up2.attribute_id = {adm_ncos_id} AND
    up2.subscriber_id = sub.id
  JOIN billing.ncos_levels nl2 ON
    nl2.id = up2.value;
"""


def fetch_prof_ncos_conflicts_query():
    return f"""
SELECT prof.id, prof.name, nl1.level, nl2.level, pp2.id
  FROM voip_subscriber_profiles prof
  JOIN voip_prof_preferences pp1 ON
    pp1.attribute_id = {ncos_id} AND
    pp1.profile_id = prof.id
  JOIN billing.ncos_levels nl1 ON
    nl1.id = pp1.value
  JOIN voip_prof_preferences pp2 ON
    pp2.attribute_id = {adm_ncos_id} AND
    pp2.profile_id = prof.id
  JOIN billing.ncos_levels nl2 ON
    nl2.id = pp2.value;
"""


def fetch_cont_ncos_conflicts_query():
    return f"""
SELECT cont.id, c.email, nl1.level, nl2.level, cp2.id
  FROM billing.contracts cont
  JOIN billing.contacts c ON
    cont.contact_id = c.id
  JOIN voip_contract_preferences cp1 ON
    cp1.attribute_id = {ncos_id} AND
    cp1.contract_id = cont.id
  JOIN billing.ncos_levels nl1 ON
    nl1.id = cp1.value
  JOIN voip_contract_preferences cp2 ON
    cp2.attribute_id = {adm_ncos_id} AND
    cp2.contract_id = cont.id
  JOIN billing.ncos_levels nl2 ON
    nl2.id = cp2.value;
"""


def fetch_dom_ncos_conflicts_query():
    return f"""
SELECT dom.id, dom.domain, nl1.level, nl2.level, dp2.id
  FROM voip_domains dom
  JOIN voip_dom_preferences dp1 ON
    dp1.attribute_id = {ncos_id} AND
    dp1.domain_id = dom.id
  JOIN billing.ncos_levels nl1 ON
    nl1.id = dp1.value
  JOIN voip_dom_preferences dp2 ON
    dp2.attribute_id = {adm_ncos_id} AND
    dp2.domain_id = dom.id
  JOIN billing.ncos_levels nl2 ON
    nl2.id = dp2.value;
"""


# SET Queries
def fetch_sub_ncos_set_conflicts_query():
    return f"""
SELECT sub.id, sub.username, ns1.name, ns2.name, up2.id
  FROM voip_subscribers sub
  JOIN voip_usr_preferences up1 ON
    up1.attribute_id = {ncos_set_id} AND
    up1.subscriber_id = sub.id
  JOIN billing.ncos_sets ns1 ON
    ns1.id = up1.value
  JOIN voip_usr_preferences up2 ON
    up2.attribute_id = {adm_ncos_set_id} AND
    up2.subscriber_id = sub.id
  JOIN billing.ncos_sets ns2 ON
    ns2.id = up2.value;
"""


def fetch_prof_ncos_set_conflicts_query():
    return f"""
SELECT prof.id, prof.name, ns1.name, ns2.name, pp2.id
  FROM voip_subscriber_profiles prof
  JOIN voip_prof_preferences pp1 ON
    pp1.attribute_id = {ncos_set_id} AND
    pp1.profile_id = prof.id
  JOIN billing.ncos_sets ns1 ON
    ns1.id = pp1.value
  JOIN voip_prof_preferences pp2 ON
    pp2.attribute_id = {adm_ncos_set_id} AND
    pp2.profile_id = prof.id
  JOIN billing.ncos_sets ns2 ON
    ns2.id = pp2.value;
"""


def fetch_cont_ncos_set_conflicts_query():
    return f"""
SELECT cont.id, c.email, ns1.name, ns2.name, cp2.id
  FROM billing.contracts cont
  JOIN billing.contacts c ON
    cont.contact_id = c.id
  JOIN voip_contract_preferences cp1 ON
    cp1.attribute_id = {ncos_set_id} AND
    cp1.contract_id = cont.id
  JOIN billing.ncos_sets ns1 ON
    ns1.id = cp1.value
  JOIN voip_contract_preferences cp2 ON
    cp2.attribute_id = {adm_ncos_set_id} AND
    cp2.contract_id = cont.id
  JOIN billing.ncos_sets ns2 ON
    ns2.id = cp2.value;
"""


def fetch_dom_ncos_set_conflicts_query():
    return f"""
SELECT dom.id, dom.domain, ns1.name, ns2.name, dp2.id
  FROM voip_domains dom
  JOIN voip_dom_preferences dp1 ON
    dp1.attribute_id = {ncos_set_id} AND
    dp1.domain_id = dom.id
  JOIN billing.ncos_sets ns1 ON
    ns1.id = dp1.value
  JOIN voip_dom_preferences dp2 ON
    dp2.attribute_id = {adm_ncos_set_id} AND
    dp2.domain_id = dom.id
  JOIN billing.ncos_sets ns2 ON
    ns2.id = dp2.value;
"""


# DELETE queries
def delete_usr_pref_by_id():
    return """
DELETE FROM voip_usr_preferences WHERE id = %s;
"""


def delete_prof_pref_by_id():
    return """
DELETE FROM voip_prof_preferences WHERE id = %s;
"""


def delete_cont_pref_by_id():
    return """
DELETE FROM voip_cont_preferences WHERE id = %s;
"""


def delete_dom_pref_by_id():
    return """
DELETE FROM voip_dom_preferences WHERE id = %s;
"""


def fetch_ncos_level_conflicts():
    conflicts = False
    sub_conflicts = []
    prof_conflicts = []
    cont_conflicts = []
    dom_conflicts = []

    cursor = db_conn.cursor()
    cursor.execute(fetch_sub_ncos_conflicts_query())
    for row in cursor:
        [sub_id, sub_username, ncos, adm_ncos, pref_id] = row
        log(
            f"Subscriber '{sub_username}' (id '{sub_id}') has assigned "
            f"ncos '{ncos}' and also adm_ncos '{adm_ncos}'"
        )
        sub_conflicts.append([pref_id])
        conflicts = True

    cursor.execute(fetch_prof_ncos_conflicts_query())
    for row in cursor:
        [prof_id, sub_name, ncos, adm_ncos, pref_id] = row
        log(
            f"Subscriber Profile '{sub_name}' (id '{prof_id}') has assigned "
            f"ncos '{ncos}' and also adm_ncos '{adm_ncos}'"
        )
        prof_conflicts.append([pref_id])
        conflicts = True

    cursor.execute(fetch_cont_ncos_conflicts_query())
    for row in cursor:
        [cont_id, cont_email, ncos, adm_ncos, pref_id] = row
        log(
            f"Customer email '{cont_email}' (id '{cont_id}') has assigned "
            f"ncos '{ncos}' and also adm_ncos '{adm_ncos}'"
        )
        cont_conflicts.append([pref_id])
        conflicts = True

    cursor.execute(fetch_dom_ncos_conflicts_query())
    for row in cursor:
        [dom_id, dom_name, ncos, adm_ncos, pref_id] = row
        log(
            f"Domain '{dom_name}' (id '{dom_id}') has assigned "
            f"ncos '{ncos}' and also adm_ncos '{adm_ncos}'"
        )
        dom_conflicts.append([pref_id])
        conflicts = True

    cursor.close()

    return (
        conflicts,
        sub_conflicts,
        prof_conflicts,
        cont_conflicts,
        dom_conflicts,
    )


def fetch_ncos_set_conflicts():
    conflicts = False
    sub_conflicts = []
    prof_conflicts = []
    cont_conflicts = []
    dom_conflicts = []

    cursor = db_conn.cursor()
    cursor.execute(fetch_sub_ncos_set_conflicts_query())
    for row in cursor:
        [sub_id, sub_username, ncos, adm_ncos, pref_id] = row
        log(
            f"Subscriber '{sub_username}' (id '{sub_id}') has assigned "
            f"ncos_set '{ncos}' and also adm_ncos_set '{adm_ncos}'"
        )
        sub_conflicts.append([pref_id])
        conflicts = True

    cursor.execute(fetch_prof_ncos_set_conflicts_query())
    for row in cursor:
        [prof_id, sub_name, ncos, adm_ncos, pref_id] = row
        log(
            f"Subscriber Profile '{sub_name}' (id '{prof_id}') has assigned "
            f"ncos_set '{ncos}' and also adm_ncos_set '{adm_ncos}'"
        )
        prof_conflicts.append([pref_id])
        conflicts = True

    cursor.execute(fetch_cont_ncos_set_conflicts_query())
    for row in cursor:
        [cont_id, cont_email, ncos, adm_ncos, pref_id] = row
        log(
            f"Customer email '{cont_email}' (id '{cont_id}') has assigned "
            f"ncos_set '{ncos}' and also adm_ncos_set '{adm_ncos}'"
        )
        cont_conflicts.append([pref_id])
        conflicts = True

    cursor.execute(fetch_dom_ncos_set_conflicts_query())
    for row in cursor:
        [dom_id, dom_name, ncos, adm_ncos, pref_id] = row
        log(
            f"Domain '{dom_name}' (id '{dom_id}') has assigned "
            f"ncos_set '{ncos}' and also adm_ncos_set '{adm_ncos}'"
        )
        dom_conflicts.append([pref_id])
        conflicts = True

    cursor.close()

    return (
        conflicts,
        sub_conflicts,
        prof_conflicts,
        cont_conflicts,
        dom_conflicts,
    )


def remove_current_adm_ncos(subs_c, prof_c, cont_c, dom_c):
    log("")
    log(
        "removing current adm_ncos/adm_ncos_set for elements with conflicts..."
    )

    cursor = db_conn.cursor()
    cursor.executemany(delete_usr_pref_by_id(), subs_c)
    cursor.executemany(delete_prof_pref_by_id(), prof_c)
    cursor.executemany(delete_cont_pref_by_id(), cont_c)
    cursor.executemany(delete_dom_pref_by_id(), dom_c)
    cursor.close()

    return


def migrate_ncos():
    log("")
    log("migrating ncos...")
    cursor = db_conn.cursor()

    cursor.execute(
        migrate_ncos_query("voip_usr_preferences"), (adm_ncos_id, ncos_id)
    )
    cursor.execute(
        migrate_ncos_query("voip_prof_preferences"), (adm_ncos_id, ncos_id)
    )
    cursor.execute(
        migrate_ncos_query("voip_contract_preferences"), (adm_ncos_id, ncos_id)
    )
    cursor.execute(
        migrate_ncos_query("voip_dom_preferences"), (adm_ncos_id, ncos_id)
    )

    cursor.execute(
        migrate_ncos_query("voip_usr_preferences"),
        (adm_ncos_set_id, ncos_set_id),
    )
    cursor.execute(
        migrate_ncos_query("voip_prof_preferences"),
        (adm_ncos_set_id, ncos_set_id),
    )
    cursor.execute(
        migrate_ncos_query("voip_contract_preferences"),
        (adm_ncos_set_id, ncos_set_id),
    )
    cursor.execute(
        migrate_ncos_query("voip_dom_preferences"),
        (adm_ncos_set_id, ncos_set_id),
    )

    cursor.close()

    return


def main():
    global args
    global ncos_id
    global adm_ncos_id
    global ncos_set_id
    global adm_ncos_set_id

    argparser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter, description=desc
    )
    argparser.add_argument(
        "--apply",
        action=argparse.BooleanOptionalAction,
        help=(
            "apply the changes, otherwise no db changes are stored by default",
        )
    )
    args, macros = argparser.parse_known_args()
    apply = args.apply

    if not apply:
        log(
            "[dry run, no changes are applied, "
            "use --apply to commit the changes]"
        )

    connect_db()

    ncos_id = get_attribute_id("ncos_id")
    adm_ncos_id = get_attribute_id("adm_ncos_id")
    ncos_set_id = get_attribute_id("ncos_set_id")
    adm_ncos_set_id = get_attribute_id("adm_ncos_set_id")

    log("checking conflicts...")
    log("")

    conflicts, subs_c, prof_c, cont_c, dom_c = fetch_ncos_level_conflicts()
    s_conflicts, s_subs_c, s_prof_c, s_cont_c, s_dom_c = (
        fetch_ncos_set_conflicts()
    )

    subs_c.extend(s_subs_c)
    prof_c.extend(s_prof_c)
    cont_c.extend(s_cont_c)
    dom_c.extend(s_dom_c)

    if not apply:
        log("")
        if conflicts or s_conflicts:
            log(
                "Please double check the above conflicts "
                "before executing the script with the '--apply' option"
            )
        else:
            log(
                "No conflicts found, you can safety execute the script "
                "with the '--apply' option"
            )
    else:
        if conflicts or s_conflicts:
            remove_current_adm_ncos(subs_c, prof_c, cont_c, dom_c)

        migrate_ncos()
        db_conn.commit()

        log("")
        log("Migration completed")

    db_conn.close()


if __name__ == "__main__":
    main()
