# Copyright 2016-2017 FUJITSU LIMITED
#
# 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 re

import falcon
from oslo_log import log
import six

from monasca_log_api.app.base import exceptions
from monasca_log_api import conf

LOG = log.getLogger(__name__)
CONF = conf.CONF

APPLICATION_TYPE_CONSTRAINTS = {
    'MAX_LENGTH': 255,
    'PATTERN': re.compile('^[a-zA-Z0-9_.\\-]+$')
}
"""Application type constraint used in validation.

See :py:func:`Validations.validate_application_type`
"""
DIMENSION_NAME_CONSTRAINTS = {
    'MAX_LENGTH': 255,
    'PATTERN': re.compile('[^><={}(), \'";&]+$')
}
"""Constraint for name of single dimension.

See :py:func:`Validations.validate_dimensions`
"""
DIMENSION_VALUE_CONSTRAINTS = {
    'MAX_LENGTH': 255
}
"""Constraint for value of single dimension.

See :py:func:`Validations.validate_dimensions`
"""


def validate_application_type(application_type=None):
    """Validates application type.

       Validation won't take place if application_type is None.
       For details see: :py:data:`APPLICATION_TYPE_CONSTRAINTS`

       :param str application_type: application type
       """

    def validate_length():
        if (len(application_type) >
                APPLICATION_TYPE_CONSTRAINTS['MAX_LENGTH']):
            msg = ('Application type {type} must be '
                   '{length} characters or less')
            raise exceptions.HTTPUnprocessableEntity(
                msg.format(
                    type=application_type,
                    length=APPLICATION_TYPE_CONSTRAINTS[
                        'MAX_LENGTH']
                )
            )

    def validate_match():
        if (not APPLICATION_TYPE_CONSTRAINTS['PATTERN']
                .match(application_type)):
            raise exceptions.HTTPUnprocessableEntity(
                'Application type %s may only contain: "a-z A-Z 0-9 _ - ."'
                % application_type
            )

    if application_type:
        validate_length()
        validate_match()


def _validate_dimension_name(name):
    try:
        if len(name) > DIMENSION_NAME_CONSTRAINTS['MAX_LENGTH']:
            raise exceptions.HTTPUnprocessableEntity(
                'Dimension name %s must be 255 characters or less' %
                name
            )
        if name[0] == '_':
            raise exceptions.HTTPUnprocessableEntity(
                'Dimension name %s cannot start with underscore (_)' %
                name
            )
        if not DIMENSION_NAME_CONSTRAINTS['PATTERN'].match(name):
            raise exceptions.HTTPUnprocessableEntity(
                'Dimension name %s may not contain: %s' %
                (name, '> < = { } ( ) \' " , ; &')
            )
    except (TypeError, IndexError):
        raise exceptions.HTTPUnprocessableEntity(
            'Dimension name cannot be empty'
        )


def _validate_dimension_value(value):
    try:
        value[0]
        if len(value) > DIMENSION_VALUE_CONSTRAINTS['MAX_LENGTH']:
            raise exceptions.HTTPUnprocessableEntity(
                'Dimension value %s must be 255 characters or less' %
                value
            )
    except (TypeError, IndexError):
        raise exceptions.HTTPUnprocessableEntity(
            'Dimension value cannot be empty'
        )


def validate_dimensions(dimensions):
    """Validates dimensions type.

       Empty dimensions are not being validated.
       For details see:

       :param dict dimensions: dimensions to validate

       * :py:data:`DIMENSION_NAME_CONSTRAINTS`
       * :py:data:`DIMENSION_VALUE_CONSTRAINTS`
       """
    try:
        for dim_name, dim_value in dimensions.items():
            _validate_dimension_name(dim_name)
            _validate_dimension_value(dim_value)
    except AttributeError:
        raise exceptions.HTTPUnprocessableEntity(
            'Dimensions %s must be a dictionary (map)' % dimensions)


def validate_content_type(req, allowed):
    """Validates content type.

    Method validates request against correct
    content type.

    If content-type cannot be established (i.e. header is missing),
    :py:class:`falcon.HTTPMissingHeader` is thrown.
    If content-type is not **application/json** or **text/plain**,
    :py:class:`falcon.HTTPUnsupportedMediaType` is thrown.


    :param falcon.Request req: current request
    :param iterable allowed: allowed content type

    :exception: :py:class:`falcon.HTTPMissingHeader`
    :exception: :py:class:`falcon.HTTPUnsupportedMediaType`
    """
    content_type = req.content_type

    LOG.debug('Content-Type is %s', content_type)

    if content_type is None or len(content_type) == 0:
        raise falcon.HTTPMissingHeader('Content-Type')

    if content_type not in allowed:
        sup_types = ', '.join(allowed)
        details = ('Only [%s] are accepted as logs representations'
                   % str(sup_types))
        raise falcon.HTTPUnsupportedMediaType(description=details)


def validate_payload_size(req):
    """Validates payload size.

    Method validates sent payload size.
    It expects that http header **Content-Length** is present.
    If it does not, method raises :py:class:`falcon.HTTPLengthRequired`.
    Otherwise values is being compared with ::

        [service]
        max_log_size = 1048576

    **max_log_size** refers to the maximum allowed content length.
    If it is exceeded :py:class:`falcon.HTTPRequestEntityTooLarge` is
    thrown.

    :param falcon.Request req: current request

    :exception: :py:class:`falcon.HTTPLengthRequired`
    :exception: :py:class:`falcon.HTTPRequestEntityTooLarge`

    """
    payload_size = req.content_length
    max_size = CONF.service.max_log_size

    LOG.debug('Payload (content-length) is %s', str(payload_size))

    if payload_size is None:
        raise falcon.HTTPLengthRequired(
            title='Content length header is missing',
            description='Content length is required to estimate if '
                        'payload can be processed'
        )

    if payload_size >= max_size:
        raise falcon.HTTPRequestEntityTooLarge(
            title='Log payload size exceeded',
            description='Maximum allowed size is %d bytes' % max_size
        )


def validate_is_delegate(roles):
    delegate_roles = CONF.roles_middleware.delegate_roles
    if roles and delegate_roles:
        roles = roles.split(',') if isinstance(roles, six.string_types) \
            else roles
        return any(x in set(delegate_roles) for x in roles)
    return False


def validate_cross_tenant(tenant_id, cross_tenant_id, roles):

    if not validate_is_delegate(roles):
        if cross_tenant_id:
            raise falcon.HTTPForbidden(
                'Permission denied',
                'Projects %s cannot POST cross tenant logs' % tenant_id
            )


def validate_log_message(log_object):
    """Validates log property.

    Log property should have message property.

    Args:
        log_object (dict): log property
       """
    if 'message' not in log_object:
        raise exceptions.HTTPUnprocessableEntity(
            'Log property should have message'
        )
