import inspect
import json

from django.apps import apps
from django.conf.urls import url
from django.core.exceptions import PermissionDenied
from django.contrib.auth import logout as auth_logout
from django.contrib.auth import views as auth_views
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, Http404
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import (RedirectView, View, TemplateView, CreateView,
        DeleteView, UpdateView, DetailView)
from django.views.generic.detail import SingleObjectMixin
from django.conf import settings
from django.db import models
from django.shortcuts import resolve_url
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from django.forms.models import modelform_factory

if 'mellon' in settings.INSTALLED_APPS:
    from mellon.utils import get_idps
else:
    get_idps = lambda: []

from passerelle.base.models import BaseResource

from .utils import to_json, response_for_json, is_authorized
from .forms import GenericConnectorForm


def get_all_apps():
    return [x for x in models.get_models() if issubclass(x, BaseResource) and \
            x.is_enabled() ]

def login(request, *args, **kwargs):
    if any(get_idps()):
        if not 'next' in request.GET:
            return HttpResponseRedirect(resolve_url('mellon_login'))
        return HttpResponseRedirect(resolve_url('mellon_login') + '?next=' + request.GET.get('next'))
    return auth_views.login(request, *args, **kwargs)

def logout(request, next_page=None):
    if any(get_idps()):
        return HttpResponseRedirect(resolve_url('mellon_logout'))
    auth_logout(request)
    if next_page is not None:
        next_page = resolve_url(next_page)
    else:
        next_page = '/'
    return HttpResponseRedirect(next_page)

def menu_json(request):
    label = _('Web Services')
    json_str = json.dumps([{'label': force_text(label),
        'slug': 'passerelle',
        'url': request.build_absolute_uri(reverse('manage-home'))
    }])
    content_type = 'application/json'
    for variable in ('jsonpCallback', 'callback'):
        if variable in request.GET:
            json_str = '%s(%s);' % (request.GET[variable], json_str)
            content_type = 'application/javascript'
            break
    response = HttpResponse(content_type=content_type)
    response.write(json_str)
    return response


class HomePageView(RedirectView):
    pattern_name = 'manage-home'
    permanent = False


class ManageView(TemplateView):
    template_name = 'passerelle/manage.html'

    def get_context_data(self, **kwargs):
        context = super(ManageView, self).get_context_data(**kwargs)
        # get all app instances
        context['apps'] = []
        for app in get_all_apps():
            context['apps'].extend(app.objects.all())
        return context


class ManageAddView(TemplateView):
    template_name = 'passerelle/manage_add.html'

    def get_context_data(self, **kwargs):
        context = super(ManageAddView, self).get_context_data(**kwargs)
        context['apps'] = get_all_apps()
        return context


class GenericConnectorMixin(object):
    def get_connector(self, **kwargs):
        return kwargs.get('connector')

    def init_stuff(self, request, *args, **kwargs):
        connector = self.get_connector(**kwargs)
        for app in apps.get_app_configs():
            if not hasattr(app, 'get_connector_model'):
                continue
            if app.get_connector_model().get_connector_slug() == connector:
                break
        else:
            raise Http404()

        self.model = app.get_connector_model()
        if hasattr(app, 'get_form_class'):
            self.form_class = app.get_form_class()
        else:
            self.form_class = modelform_factory(self.model,
                    form=GenericConnectorForm, exclude=('slug', 'users'))

    def dispatch(self, request, *args, **kwargs):
        self.init_stuff(request, *args, **kwargs)
        return super(GenericConnectorMixin, self).dispatch(
                request, *args, **kwargs)


class GenericConnectorView(GenericConnectorMixin, DetailView):
    def get_template_names(self):
        template_names = super(DetailView, self).get_template_names()[:]
        if self.model.manager_view_template_name:
            template_names.append(self.model.manager_view_template_name)
        template_names.append('passerelle/manage/service_view.html')
        return template_names


class GenericCreateConnectorView(GenericConnectorMixin, CreateView):
    template_name = 'passerelle/manage/service_form.html'


class GenericEditConnectorView(GenericConnectorMixin, UpdateView):
    template_name = 'passerelle/manage/service_form.html'


class GenericDeleteConnectorView(GenericConnectorMixin, DeleteView):
    template_name = 'passerelle/manage/service_confirm_delete.html'

    def get_success_url(self):
        return reverse('manage-home')


class WrongParameter(Exception):
    http_status = 400
    log_error = False

    def __init__(self, missing, extra):
        self.missing = missing
        self.extra = extra

    def __str__(self):
        s = []
        if self.missing:
            s.append('missing parameters: %s.' % ', '.join(map(repr, self.missing)))
        if self.extra:
            s.append('extra parameters: %s.' % ', '.join(map(repr, self.extra)))
        return ' '.join(s)

IGNORED_PARAMS = ('apikey', 'signature', 'nonce', 'algo', 'timestamp', 'orig', 'jsonpCallback',
                  'callback', '_', 'raise', 'debug', 'decode', 'format')


class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View):
    def get_params(self, request, parameters=None, *args, **kwargs):
        d = {}
        for key in request.GET:
            # ignore authentication keys and JSONP params
            if key in IGNORED_PARAMS:
                continue
            d[key] = request.GET[key]
        other_params = kwargs.get('other_params', {})
        for key in other_params:
            if other_params[key] is None:
                continue
            if not d.get(key):
                d[key] = other_params[key]
        return d

    @csrf_exempt
    def dispatch(self, request, *args, **kwargs):
        self.init_stuff(request, *args, **kwargs)
        connector = self.get_object()
        self.endpoint = None
        endpoints = []
        for name, method in inspect.getmembers(connector):
            if not hasattr(method, 'endpoint_info'):
                continue
            if not method.endpoint_info.name == kwargs.get('endpoint'):
                continue
            if method.endpoint_info.pattern:
                pattern = url(method.endpoint_info.pattern, method)
                match = pattern.resolve(kwargs.get('rest') or '')
                if match:
                    self.endpoint = method
                    break
            else:
                self.endpoint = method
        if not self.endpoint:
            raise Http404()
        return super(GenericEndpointView, self).dispatch(request, *args, **kwargs)

    def _allowed_methods(self):
        return [x.upper() for x in self.endpoint.endpoint_info.methods]

    def check_perms(self, request):
        perm = self.endpoint.endpoint_info.perm
        if not perm:
            return True
        return is_authorized(request, self.get_object(), perm)

    def perform(self, request, *args, **kwargs):
        if request.method.lower() not in self.endpoint.endpoint_info.methods:
            return self.http_method_not_allowed(request, *args, **kwargs)
        if not self.check_perms(request):
            raise PermissionDenied()
        argspec = inspect.getargspec(self.endpoint)
        parameters = argspec.args[2:]
        try:
            params = self.get_params(request, parameters=parameters, *args, **kwargs)
            inspect.getcallargs(self.endpoint, request, **params)
        except TypeError:
            # prevent errors if using name of an ignored parameter in an endpoint argspec
            ignored = set(parameters) & set(IGNORED_PARAMS)
            assert not ignored, 'endpoint %s has ignored parameter %s' % (request.path, ignored)
            extra, missing = [], []
            for i, arg in enumerate(parameters):
                # check if the argument is optional, i.e. it has a default value
                if len(parameters) - i <= len(argspec.defaults or []):
                    continue
                if arg not in params:
                    missing.append(arg)
            for key in params:
                if key not in argspec.args:
                    extra.append(key)
            raise WrongParameter(missing, extra)

        # auto log request's inputs
        connector_name, endpoint_name = kwargs['connector'], kwargs['endpoint']
        connector = self.get_object()
        url = request.get_full_path()
        payload = request.body[:5000]
        connector.logger.debug('endpoint %s %s (%r) ' %
                               (request.method, url, payload),
                               extra={
                                   'connector': connector_name,
                                   'connector_endpoint': endpoint_name,
                                   'connector_endpoint_method': request.method,
                                   'connector_endpoint_url': url,
                                   'connector_payload': payload
                               })

        return self.endpoint(request, **self.get_params(request, *args, **kwargs))

    def get(self, request, *args, **kwargs):
        if self.endpoint.endpoint_info.serializer_type == 'json-api':
            json_decorator = to_json('api', wrap_response=self.endpoint.endpoint_info.wrap_response)
        else:
            json_decorator = to_json('plain', wrap_response=self.endpoint.endpoint_info.wrap_response)
        if self.endpoint.endpoint_info.pattern:
            pattern = url(self.endpoint.endpoint_info.pattern, self.endpoint)
            match = pattern.resolve(kwargs.get('rest') or '')
            if not match:
                raise Http404()
            kwargs['other_params'] = match.kwargs
        elif kwargs.get('rest'):
            raise Http404()
        return json_decorator(self.perform)(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.get(request, *args, **kwargs)


# legacy
LEGACY_APPS_PATTERNS = {
    'datasources': {'url': 'data', 'name': 'Data Sources'},
    'repost': {'url': 'repost', 'name': 'Re-Post'},
    'register': {'url': r'register', 'name': 'Register'},
    'queue': {'url': r'queue', 'name': 'Queues'},
}

class LegacyPageView(TemplateView):
    template_name = 'passerelle/legacy.html'

    def get_context_data(self, **kwargs):
        context = super(LegacyPageView, self).get_context_data(**kwargs)
        # legacy apps (categories)
        context['legacy_apps'] = []
        for app in (app for app in settings.INSTALLED_APPS
                if app.startswith('passerelle.')
                    and app.split('.')[1] in LEGACY_APPS_PATTERNS):
            context['legacy_apps'].append(LEGACY_APPS_PATTERNS[app.split('.')[1]])
        return context
