# Copyright (C) 2015 Catalyst IT Ltd
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

import hashlib
import json

from datetime import timedelta
from smtplib import SMTPException
from uuid import uuid4

from decorator import decorator

from django.conf import settings
from django.core.exceptions import FieldError
from django.core.mail import EmailMultiAlternatives
from django.template import loader
from django.utils import timezone

from rest_framework.response import Response

from adjutant.api.models import Notification, Token


def create_token(task):
    expire = timezone.now() + timedelta(hours=settings.TOKEN_EXPIRE_TIME)

    uuid = uuid4().hex
    token = Token.objects.create(
        task=task,
        token=uuid,
        expires=expire
    )
    token.save()
    return token


def send_stage_email(task, email_conf, token=None):
    if not email_conf:
        return

    text_template = loader.get_template(
        email_conf['template'],
        using='include_etc_templates')
    html_template = email_conf.get('html_template', None)
    if html_template:
        html_template = loader.get_template(
            html_template,
            using='include_etc_templates')

    emails = set()
    actions = {}
    # find our set of emails and actions that require email
    for action in task.actions:
        act = action.get_action()
        email = act.get_email()
        if email:
            emails.add(email)
            actions[str(act)] = act

    if not emails:
        return

    if len(emails) > 1:
        notes = {
            'errors':
            ("Error: Unable to send update, more than one email for task: %s"
             % task.uuid)
        }
        create_notification(task, notes, error=True)
        return

    context = {
        'task': task,
        'actions': actions
    }
    if token:
        if settings.HORIZON_URL:
            tokenurl = settings.HORIZON_URL
            if not tokenurl.endswith('/'):
                tokenurl += '/'
            tokenurl += 'token/'
        else:
            tokenurl = settings.TOKEN_SUBMISSION_URL
            if not tokenurl.endswith('/'):
                tokenurl += '/'
        context.update({
            'tokenurl': tokenurl,
            'token': token.token
        })

    try:
        message = text_template.render(context)

        # from_email is the return-path and is distinct from the
        # message headers
        from_email = email_conf.get('from')
        if not from_email:
            from_email = email_conf['reply']
        elif "%(task_uuid)s" in from_email:
            from_email = from_email % {'task_uuid': task.uuid}

        # these are the message headers which will be visible to
        # the email client.
        headers = {
            'X-Adjutant-Task-UUID': task.uuid,
            # From needs to be set to be disctinct from return-path
            'From': email_conf['reply'],
            'Reply-To': email_conf['reply'],
        }

        email = EmailMultiAlternatives(
            email_conf['subject'],
            message,
            from_email,
            [emails.pop()],
            headers=headers,
        )

        if html_template:
            email.attach_alternative(
                html_template.render(context), "text/html")

        email.send(fail_silently=False)

    except SMTPException as e:
        notes = {
            'errors':
                ("Error: '%s' while emailing update for task: %s" %
                    (e, task.uuid))
        }

        errors_conf = settings.TASK_SETTINGS.get(
            task.task_type, settings.DEFAULT_TASK_SETTINGS).get(
                'errors', {}).get("SMTPException", {})

        if errors_conf:
            notification = create_notification(
                task, notes, error=True,
                engines=errors_conf.get('engines', True))

            if errors_conf.get('notification') == "acknowledge":
                notification.acknowledged = True
                notification.save()
        else:
            create_notification(task, notes, error=True)


def create_notification(task, notes, error=False, engines=True):
    notification = Notification.objects.create(
        task=task,
        notes=notes,
        error=error
    )
    notification.save()

    if not engines:
        return notification

    class_conf = settings.TASK_SETTINGS.get(
        task.task_type, settings.DEFAULT_TASK_SETTINGS)

    notification_conf = class_conf.get('notifications', {})

    if notification_conf:
        for note_engine, conf in notification_conf.items():
            if error:
                conf = conf.get('error', {})
            else:
                conf = conf.get('standard', {})
            if not conf:
                continue
            engine = settings.NOTIFICATION_ENGINES[note_engine](conf)
            engine.notify(task, notification)

    return notification


def create_task_hash(task_type, action_list):
    hashable_list = [task_type, ]

    for action in action_list:
        hashable_list.append(action['name'])
        if not action['serializer']:
            continue
        # iterate like this to maintain consistent order for hash
        fields = sorted(action['serializer'].validated_data.keys())
        for field in fields:
            try:
                hashable_list.append(
                    action['serializer'].validated_data[field])
            except KeyError:
                if field is "username" and settings.USERNAME_IS_EMAIL:
                    continue
                else:
                    raise

    return hashlib.sha256(str(hashable_list).encode('utf-8')).hexdigest()


# "{'filters': {'fieldname': { 'operation': 'value'}}
@decorator
def parse_filters(func, *args, **kwargs):
    """
    Parses incoming filters paramters and converts them to
    Django usable operations if valid.

    BE AWARE! WILL NOT WORK UNLESS POSITIONAL ARGUMENT 3 IS FILTERS!
    """
    request = args[1]
    filters = request.query_params.get('filters', None)

    if not filters:
        return func(*args, **kwargs)
    cleaned_filters = {}
    try:
        filters = json.loads(filters)
        for field, operations in filters.items():
            for operation, value in operations.items():
                cleaned_filters['%s__%s' % (field, operation)] = value
    except (ValueError, AttributeError):
        return Response(
            {'errors': [
                "Filters incorrectly formatted. Required format: "
                "{'filters': {'fieldname': { 'operation': 'value'}}"
            ]},
            status=400
        )

    try:
        # NOTE(adriant): This feels dirty and unclear, but it works.
        # Positional argument 3 is filters, so we just replace it.
        args = list(args)
        args[2] = cleaned_filters
        return func(*args, **kwargs)
    except FieldError as e:
            return Response({'errors': [str(e)]}, status=400)


def add_task_id_for_roles(request, processed, response_dict, req_roles):
    if request.keystone_user.get('authenticated', False):

        req_roles = set(req_roles)
        roles = set(request.keystone_user.get('roles', []))

        if roles & req_roles:
            response_dict['task'] = processed['task'].uuid
