# (c) cavaliba.com - sirene - notify.py

import json
import uuid
from datetime import datetime, timedelta

import app_home.cache as cache
import html2text
from app_data.data import Instance, get_instances, get_instances_raw_json
from app_home.configuration import get_configuration
from app_home.log import DEBUG, WARNING, log
from app_sirene.mail import sirene_html_email, task_send_mail
from app_sirene.sms import get_sms_quota, task_send_sms
from app_user.group import group_get_by_name, group_get_users
from app_user.user import user_get_by_login, user_get_email, user_get_mobile
from celery import shared_task
from django.utils import timezone


# ------------------------------------------------------------
# tasks
# ------------------------------------------------------------
@shared_task(ignore_result=True)
def archive_expired_sirene(aaa=None):
    cache.init()
    count = archive_expired()
    log(DEBUG, aaa=aaa, app="sirene", view="task", action="archive", status="OK", data=f"{count} archived")


# ------------------------------------------------------------
# helpers
# ------------------------------------------------------------

def get_username(aaa=None):

    if aaa:
        return aaa.get("username", "auto")
    else:
        return "auto"


def aaa_message_allowed(instance=None, aaa=None):
    '''
    Check if instance() of sirene_message can be accessed by the aaa user. 
    For detail/remove/archive/... views
    '''

    if not instance.is_field_true('is_restricted'):
        return True

    if 'p_sirene_access_restricted' in aaa["perms"]:
        return True

    try:
        username = aaa['username']
    except Exception:
        return False

    try:
        if username in instance.fields['notified_username'].value:
            return True
    except Exception:
        pass
    return False


def is_24():
    ''' return True if current time outside business hours  Mo-Fr 08h00-18h00'''
    dt = timezone.now()
    wd = dt.weekday()  # Week =0-4 WEnd=5/6
    ho = dt.hour

    if wd > 4:
        return True

    if ho < 8:
        return True

    if ho > 17:
        return True

    return False



def archive_message(id=None, aaa=None):

    message = get_message_by_id(id=id)
    if not message:
        return "no message"


    if aaa:
        username = aaa.get("username", "")
    else:
        username = "auto"

    message.set_field_value_single('removed_at', str(datetime.today().strftime('%Y-%m-%d %H:%M:%S')) )
    message.set_field_value_single('removed_by', username)
    message.is_enabled = False
    message.update()

    return





def archive_all(aaa=None):

    count = 0

    instances = get_message_enabled()
    if not instances:
        return count

    for instance in instances:
        err = archive_message(id=instance.id, aaa=aaa)
        if not err:
            count += 1

    return count


def archive_expired(aaa=None):

    count = 0
    default_duration = int(get_configuration("sirene", "DEFAULT_DURATION_MINUTE"))

    instances = get_message_enabled()
    if not instances:
        return count

    for instance in instances:

        datestr1 = instance.get_attribute_first('created_at')
        datestr2 = instance.get_attribute_first('updated_at')
        if datestr2:
            datestr = datestr2
        else:
            datestr = datestr1
        try:
            date = datetime.strptime(datestr, '%Y-%m-%d %H:%M:%S')
        except Exception:
            continue
        date_expire = date + timedelta(minutes=default_duration)
        date_now = datetime.now()
        if date_expire <= date_now:
            # to archive
            err = archive_message(id=instance.id)
            if not err:
                count += 1
                log(DEBUG, aaa=aaa, app="sirene", view="archive", action="expired", status="OK",
                    data=f"{instance.id}")
            else:
                log(WARNING, aaa=aaa, app="sirene", view="archive", action="expired", status="KO",
                    data=f"{instance.id}")

    return count


# -------------------------------------------------------------------------
# reopen
# -------------------------------------------------------------------------

def reopen_message(instance=None):

    if not instance:
        return False

    instance.is_enabled = True
    instance.update()

    return True


# -------------------------------------------------------------------------
# getter
# -------------------------------------------------------------------------

def get_message_by_id(id):
    instance = Instance.from_id(id, expand=True)
    if instance.classname == "sirene_message":
        return instance



def get_message_enabled():
    ''' OUT: list[] of sirene_message Instance()'''

    reply = []

    db_messages = get_instances(classname="sirene_message" , is_enabled=True)

    for iobj in db_messages:
        instance = Instance(iobj=iobj, expand=True)
        if not instance:
            continue
        reply.append(instance)

    return reply



def get_public_active():

    # OUT: list[] of sirene_public Instance() for each sirene_message
    # is_enabled message only
    # is_enabled public page only


    reply = []

    db_messages = get_instances(classname="sirene_message" , is_enabled=True)

    for iobj in db_messages:
        instance = Instance(iobj=iobj)

        # exclude private (restricted) message from anonymous
        if instance.is_field_true('is_restricted'):
            continue

        # get public page
        public_name = instance.fields['public_page'].get_first_value()
        instance_public = Instance.from_keyname(classname="sirene_public", keyname=public_name)


        # exclude messages with no public page
        if not instance_public:
            continue
        if not instance_public.keyname:
            continue
        if not instance_public.is_enabled:
            continue

        # skip "default" (ok) message ;displayed only for an empty screen
        if instance_public.is_field_true('is_default'):
            continue

        reply.append(instance_public)

    return reply



def get_public_default():
    # OUT: [Instance()]

    db_publics = get_instances(classname="sirene_public" , is_enabled=True)
    if not db_publics:
        return []

    for pub in db_publics:
        instance = Instance(iobj=pub)
        if not instance:
            continue
        if instance.is_field_true('is_default'):
            return [instance]
    return []


def textify(html):
    text = html2text.html2text(html)
    return text


# -------------------------------------------------------------------------
# get_update
# -------------------------------------------------------------------------
def get_updates(instance = None):

    if not instance:
        return

    try:
        v = json.loads(instance.fields['update'].value[0])
        if type(v) is not list:
            v = []
    except Exception:
        v= []

    return v


def set_updates(instance=None, updates=None):

    if not instance:
        return False

    try:
        v = json.dumps(updates, ensure_ascii=False)
    except Exception:
        return False

    try:
        instance.fields['update'].value = [v]
    except Exception:
        return False

    return True


# -------------------------------------------------------------------------
# count people/email/sms from Instance notify_to fields / has_email or sms
# -------------------------------------------------------------------------
def count_userlist(instance=None, maxcount=0):


    userlist = expand(instance=instance, maxcount=maxcount)

    usercount = len(userlist)
    if instance.is_field_true("has_email"):
        emailcount = len(get_email_list(userlist))
    else:
        emailcount = 0
    if instance.is_field_true("has_sms"):
        smscount = len(get_sms_list(userlist))
    else:
        smscount = 0

    return usercount, emailcount, smscount

# -------------------------------------------------------------------------
#
# -------------------------------------------------------------------------
def get_email_list(userlist):

    # set instead of array [] for unique values
    email_list = set()

    for user in userlist:

        if not user.want_notifications:
            continue

        if not user.want_24:
            if is_24():
                continue

        email = user_get_email(user)

        if email:
            email_list.add(email)

    return list(email_list)

# -------------------------------------------------------------------------
#
# -------------------------------------------------------------------------
def get_sms_list(userlist):

    # set instead of array [] for unique values
    sms_list = set()


    for user in userlist:

        if not user.is_enabled:
            continue
        if not user.want_notifications:
            continue
        if not user.want_24:
            if is_24():
                continue

        mobile = user_get_mobile(user)
        if mobile:
            sms_list.add(mobile)

    return list(sms_list)



def set_placeholder(data, message):

    data2 = data

    if '$C$' in data2:
        try:
            data2 = data2.replace('$C$', message.fields['category'].value[0])
        except Exception:
            pass
    if '$S$' in data2:
        try:
            data2 = data2.replace('$S$', message.fields['severity'].value[0])
        except Exception:
            pass

    return data2



# -------------------------------------------------------------------------
# NOTIFICATION 2  - v3.26
# IN: POST REQUEST
# OUT: sirene_message Instance saved to DB if valid, or None
# -------------------------------------------------------------------------
def message_from_request(request=None, aaa=None):

    if not request:
        return None

    message = Instance(classname="sirene_message")
    message.merge_request(request, aaa=aaa)
    message.keyname = str(uuid.uuid4())
    message.is_enabled = True
    # TODO: store template name

    # no public page if is_restricted
    try:
        if message.is_field_true('is_restricted'):
            message.fields['public_page'].value = []
    except Exception:
        pass

    message.fields['created_at'].value = [str(datetime.today().strftime('%Y-%m-%d %H:%M:%S'))]


    # placeholder : $C$ = category : $S$ = severity
    message.displayname = set_placeholder(message.displayname, message)
    content = message.get_attribute_first('content')
    message.fields['content'].value = [ set_placeholder(content, message) ]


    try:
        message.fields['created_by'].value = [get_username(aaa=aaa)]
    except Exception:
        message.fields['created_by'].value = 'n/a'

    message.fields['created_at'].value = [str(datetime.today().strftime('%Y-%m-%d %H:%M:%S'))]

    if not message.is_valid():
        return

    message.create()
    return message



def message_notify(message=None, aaa=None):

    if not message:
        return False

    # compute targeted users
    user_max = int(get_configuration("sirene","USER_MAX_NOTIFICATION"))
    userlist = expand(instance=message, maxcount=user_max)

    message.fields['notified_username'].value =  [u.login for u in userlist]
    message.update()

    # send email
    #  ----------
    email_list = get_email_list(userlist)
    email_count = len(email_list)

    if not message.is_field_true("has_email"):
        email_count = 0

    email_max = int(get_configuration("sirene","EMAIL_MAX_NOTIFICATION"))
    if email_count > email_max:
        email_count = 0

    if email_count > 0:

        prefix = get_configuration("sirene", "EMAIL_PREFIX")
        prefix = set_placeholder(prefix, message)
        subject = prefix + ' ' + message.displayname

        html_content = sirene_html_email(message=message)
        text_content = textify(html_content)
        try:
            task_send_mail.delay(subject, text_content, email_list, html_content=html_content, aaa=aaa)
        except Exception as e:
            print("task exception: ", e)
            email_count = 0

    # send sms
    # --------
    sms_list = get_sms_list(userlist)
    sms_count = len(sms_list)

    if not message.is_field_true('has_sms'):
        sms_count = 0

    sms_max = int(get_configuration("sirene","SMS_MAX_NOTIFICATION"))
    if sms_count > sms_max:
        sms_count = 0

    body = message.get_attribute_first('sms_content')
    if len(body) == 0:
        sms_count = 0

    if sms_count > 0:
        sms_left = get_sms_quota(aaa)
        if sms_left - sms_count >= 0:
            prefix = get_configuration("sirene", "SMS_PREFIX")
            prefix = set_placeholder(prefix, message)
            #text_content = prefix + ' ' + nm.displayname
            #body = message.get_attribute_first('sms_content')
            #body = textify(body)
            text_content = prefix + ' ' + body
            try:
                task_send_sms.delay(sms_list, text_content, aaa=aaa)
            except Exception as e:
                print("task exception: ", e)
                sms_count = 0



    message.set_field_value_single('email_count', email_count)
    message.set_field_value_single('sms_count', sms_count)
    message.update()

    return True

# -------------------------------------------------------------------------
# UPDATE
# IN
# - instance is a sirene_message
# - mu is a dict (has_email, has_sms, content, sms_content, created_at, created_by)
# OUT: err, email_count, sms_count
# -------------------------------------------------------------------------
def create_update(instance=None, aaa=None, mu=None):

    if not instance:
        return "no instance",0,0

    if not mu:
        return "no update",0,0

    # from view/form: content, has_email, has_sms
    try:
        mu['created_by'] = get_username(aaa=aaa)
    except Exception:
        mu['created_by'] = "n/a"

    mu['created_at'] = str(datetime.today().strftime('%Y-%m-%d %H:%M:%S'))

    instance.fields['updated_at'].value = [mu['created_at']]
    instance.fields['updated_by'].value = [mu['created_by']]

    # Notify update
    user_max = int(get_configuration("sirene","USER_MAX_NOTIFICATION"))
    userlist = expand(instance=instance, maxcount=user_max)

    # placeholder $C$ $S$ in update content
    mu['content'] = set_placeholder(mu['content'], instance)


    # email
    #  -----
    email_list = get_email_list(userlist)
    email_count = len(email_list)

    if not mu['has_email']:
        email_count = 0

    email_max = int(get_configuration("sirene","EMAIL_MAX_NOTIFICATION"))
    if email_count > email_max:
        email_count = 0

    if email_count > 0:
        title = instance.displayname

        prefix = get_configuration("sirene", "EMAIL_UPDATE_PREFIX")
        prefix = set_placeholder(prefix, instance)
        subject = prefix + ' ' + title

        updates = get_updates(instance = instance)
        updates.append(mu)
        html_content = sirene_html_email(message=instance, updates=updates)
        text_content = textify(html_content)

        task_send_mail.delay(subject, text_content, email_list, html_content=html_content, aaa=aaa)

    # sms
    # ---
    sms_list = get_sms_list(userlist)
    sms_count = len(sms_list)

    if not mu['has_sms']:
        sms_count = 0

    body = mu['sms_content']
    if len(body)==0:
        sms_count = 0

    sms_max = int(get_configuration("sirene","SMS_MAX_NOTIFICATION"))
    if sms_count > sms_max:
        sms_count = 0

    if sms_count > 0:
        sms_left = get_sms_quota(aaa)
        if sms_left - sms_count >= 0:
            prefix = get_configuration("sirene", "SMS_UPDATE_PREFIX")
            prefix = set_placeholder(prefix, instance)
            # body = mu['sms_content']
            # body = textify(body)
            text_content = prefix + ' ' + body
            task_send_sms.delay(sms_list, text_content, aaa=aaa)

    # save update
    # -----------
    mu['email_count'] = email_count
    mu['sms_count'] = sms_count
    # total_email = instance.fields['email_count'].value[0] + email_count
    # total_sms   = instance.fields['sms_count'].value[0] + sms_count
    # instance.set_field_value_single('email_count', total_email)
    # instance.set_field_value_single('sms_count', total_sms)
    v = get_updates(instance = instance)
    v.append(mu)
    r = set_updates(instance, v)
    instance.update()


    return None, email_count, sms_count


# -------------------------------------------------------------------------
# expand Instance sirene_message to userlist of db User objects to notify
# -------------------------------------------------------------------------
# subscriptions = [ ]
def init_watchlist():

    reply = {}
    # site:app  x:y z:t ...
    notify_subscriptions = get_configuration('sirene','NOTIFY_SUBSCRIPTIONS')
    subscriptions = notify_subscriptions.split()
    for subscription in subscriptions:
        if ':' in subscription:
            try:
                (subscriber_schema, subscribed_schema) = subscription.split(':')
                reply[subscribed_schema] = []
            except Exception:
                pass
    return reply

def expand(instance=None, maxcount=0):
    ''' 
    compute list of targeted users limited to maxcount
    OUT: [user_obj, user_obj, ...]
    is_enabled users only ; not filtered by user prefs
    '''

    allusers = []
    allgroups = []

    if not instance:
        return []

    tmp = get_configuration('sirene','NOTIFY_FIELDS')
    try:
        notify_fields = tmp.split()
    except Exception:
        notify_fields = ['user', 'group']


    # dict of empty list : subscribed schemas (site:app >>> gives app)
    watchlist = init_watchlist()

    for targetfield in notify_fields:


        # direct
        # ------

        if targetfield == "user":
            if 'notify_user' in instance.fields:
                for  username in instance.fields['notify_user'].value:
                    #print("   username:", username)
                    userobj = user_get_by_login(username)
                    if not userobj:
                        continue
                    if not userobj.is_enabled:
                        continue

                    allusers.append(userobj)


        #  targetfield == notify_group => groups (expand at the end)
        elif targetfield == "group":
            if 'notify_group' in instance.fields:
                for groupname in instance.fields["notify_group"].value:
                    #print("   group:", groupname)
                    groupobj = group_get_by_name(groupname)
                    if not groupobj:
                        continue
                    if not groupobj.is_enabled:
                        continue

                    if groupobj not in allgroups:
                        allgroups.append(groupobj)


        # targetfield is a schema
        else:
            schema = targetfield
            fieldname = "notify_" + schema

            if fieldname not in instance.fields:
                continue

            # loop over each value in notify_xxxx field
            for iname in instance.fields[fieldname].value:
                subinstance = Instance.from_keyname(classname=schema, keyname=iname)

                if not subinstance:
                    continue

                # update watchlist for subscription
                if schema in watchlist:
                    watchlist[schema].append(iname)


                # nested user
                if 'notify_user' in subinstance.fields:
                    for  username in subinstance.fields["notify_user"].value:
                        userobj = user_get_by_login(username)
                        if not userobj:
                            continue
                        if not userobj.is_enabled:
                            continue
                        allusers.append(userobj)

                #  nested group
                if 'notify_group' in subinstance.fields:
                    for groupname in subinstance.fields["notify_group"].value:
                        groupobj = group_get_by_name(groupname)
                        if not groupobj:
                            continue
                        if not groupobj.is_enabled:
                            continue
                        if groupobj not in allgroups:
                            allgroups.append(groupobj)


    # indirect (subscriptions)
    # subscription == site:app  => site subscribed to app(s) through notify_app field
    subscriptions_conf = get_configuration('sirene','NOTIFY_SUBSCRIPTIONS')
    subscriptions = subscriptions_conf.split()

    for subscription in subscriptions:

        if ':' in subscription:
            try:
                (subscriber_schema, subscribed_schema) = subscription.split(':')
            except Exception:
                continue

            subscription_field = "notify_" + subscribed_schema

            if subscribed_schema not in watchlist:
                continue
            if len(watchlist[subscribed_schema]) == 0:
                continue

            # get all subscribed instance from all subscribers  : {} keyname => field value
            subscribers = get_instances_raw_json(classname = subscriber_schema, fieldname = subscription_field)

            # instances : site1 : app1
            for  subscriber, subscribed in subscribers.items():
                #print ("     SUBSCRIPTION computed:", subscriber, subscribed)

                #  intersect subscribed lists and watchlist
                found = False
                for i in subscribed:
                    if i in watchlist[subscribed_schema]:
                        found = True
                        break

                # this subscriber has not subscribed to an instance encountered (watchlist)
                if not found:
                    continue

                #  add this subscriber's user/group !

                # Get INSTANCE
                subinstance = Instance.from_keyname(classname=subscriber_schema, keyname=subscriber)
                if not subinstance:
                    continue

                # nested user
                if 'notify_user' in subinstance.fields:
                    for  username in subinstance.fields["notify_user"].value:
                        #print("   subscription sub username:", username)
                        userobj = user_get_by_login(username)
                        if not userobj:
                            continue
                        if not userobj.is_enabled:
                            continue

                        allusers.append(userobj)

                #  nested group
                if 'notify_group' in subinstance.fields:
                    for groupname in subinstance.fields["notify_group"].value:
                        #print("   subscription sub group:", groupname)
                        groupobj = group_get_by_name(groupname)
                        if not groupobj:
                            continue
                        if not groupobj.is_enabled:
                            continue

                        if groupobj not in allgroups:
                            allgroups.append(groupobj)

    #  TODO : limit to max_user
    allusers_fromgroup = []
    for group in allgroups:
        allusers_fromgroup += group_get_users(group, maxcount=maxcount)
    allusers = allusers + allusers_fromgroup

    # unique values
    return set(allusers)




