import functools
import logging

from ..decorators.decorator import (BeginOfRequestLifecycleDecorator,
                                    EndOfRequestLifecycleDecorator)
from ..decorators.security import (get_apikeyinfo_func, get_basicinfo_func,
                                   get_scope_validate_func, get_tokeninfo_func,
                                   security_deny, security_passthrough,
                                   verify_apikey, verify_basic, verify_oauth,
                                   verify_security)

logger = logging.getLogger("connexion.operations.secure")

DEFAULT_MIMETYPE = 'application/json'


class SecureOperation(object):

    def __init__(self, api, security, security_schemes):
        """
        :param security: list of security rules the application uses by default
        :type security: list
        :param security_definitions: `Security Definitions Object
            <https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object>`_
        :type security_definitions: dict
        """
        self._api = api
        self._security = security
        self._security_schemes = security_schemes

    @property
    def api(self):
        return self._api

    @property
    def security(self):
        return self._security

    @property
    def security_schemes(self):
        return self._security_schemes

    @property
    def security_decorator(self):
        """
        Gets the security decorator for operation

        From Swagger Specification:

        **Security Definitions Object**

        A declaration of the security schemes available to be used in the specification.

        This does not enforce the security schemes on the operations and only serves to provide the relevant details
        for each scheme.


        **Operation Object -> security**

        A declaration of which security schemes are applied for this operation. The list of values describes alternative
        security schemes that can be used (that is, there is a logical OR between the security requirements).
        This definition overrides any declared top-level security. To remove a top-level security declaration,
        an empty array can be used.


        **Security Requirement Object**

        Lists the required security schemes to execute this operation. The object can have multiple security schemes
        declared in it which are all required (that is, there is a logical AND between the schemes).

        The name used for each property **MUST** correspond to a security scheme declared in the Security Definitions.

        :rtype: types.FunctionType
        """
        logger.debug('... Security: %s', self.security, extra=vars(self))
        if not self.security:
            return security_passthrough

        auth_funcs = []
        required_scopes = None
        for security_req in self.security:
            if not security_req:
                continue
            elif len(security_req) > 1:
                logger.warning("... More than one security scheme in security requirement defined. "
                               "**DENYING ALL REQUESTS**", extra=vars(self))
                return security_deny

            scheme_name, scopes = next(iter(security_req.items()))
            security_scheme = self.security_schemes[scheme_name]

            if security_scheme['type'] == 'oauth2':
                required_scopes = scopes
                token_info_func = get_tokeninfo_func(security_scheme)
                scope_validate_func = get_scope_validate_func(security_scheme)
                if not token_info_func:
                    logger.warning("... x-tokenInfoFunc missing", extra=vars(self))
                    continue

                auth_funcs.append(verify_oauth(token_info_func, scope_validate_func))

            # Swagger 2.0
            elif security_scheme['type'] == 'basic':
                basic_info_func = get_basicinfo_func(security_scheme)
                if not basic_info_func:
                    logger.warning("... x-basicInfoFunc missing", extra=vars(self))
                    continue

                auth_funcs.append(verify_basic(basic_info_func))

            # OpenAPI 3.0.0
            elif security_scheme['type'] == 'http':
                scheme = security_scheme['scheme'].lower()
                if scheme == 'basic':
                    basic_info_func = get_basicinfo_func(security_scheme)
                    if not basic_info_func:
                        logger.warning("... x-basicInfoFunc missing", extra=vars(self))
                        continue

                    auth_funcs.append(verify_basic(basic_info_func))
                else:
                    logger.warning("... Unsupported http authorization scheme %s" % scheme, extra=vars(self))

            elif security_scheme['type'] == 'apiKey':
                apikey_info_func = get_apikeyinfo_func(security_scheme)
                if not apikey_info_func:
                    logger.warning("... x-apikeyInfoFunc missing", extra=vars(self))
                    continue

                auth_funcs.append(verify_apikey(apikey_info_func, security_scheme['in'], security_scheme['name']))

            else:
                logger.warning("... Unsupported security scheme type %s" % security_scheme['type'], extra=vars(self))

        return functools.partial(verify_security, auth_funcs, required_scopes)

    def get_mimetype(self):
        return DEFAULT_MIMETYPE

    @property
    def _request_begin_lifecycle_decorator(self):
        """
        Transforms the result of the operation handler in a internal
        representation (connexion.lifecycle.ConnexionRequest) to be
        used by internal Connexion decorators.

        :rtype: types.FunctionType
        """
        return BeginOfRequestLifecycleDecorator(self.api, self.get_mimetype())

    @property
    def _request_end_lifecycle_decorator(self):
        """
        Guarantees that instead of the internal representation of the
        operation handler response
        (connexion.lifecycle.ConnexionRequest) a framework specific
        object is returned.
        :rtype: types.FunctionType
        """
        return EndOfRequestLifecycleDecorator(self.api, self.get_mimetype())
