# passerelle.contrib.agoraplus
# Copyright (C) 2015  Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import json
import unicodedata
import logging

from django.core.exceptions import ObjectDoesNotExist
from django.views.generic import DetailView as GenericDetailView
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse, HttpResponseBadRequest, Http404, HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from django.utils.http import urlencode

from passerelle import utils

from .models import AgoraPlus, AgoraPlusLink, AgoraAPIError
from .wcs import Formdata
from .provisioning import provision_attributes, provision_roles


logger = logging.getLogger('passerelle.contrib.agoraplus')


def normalize_lower(s):
    return unicodedata.normalize('NFKD', s).encode('ascii', 'ignore').lower()


class AgoraPlusDetailView(GenericDetailView):
    model = AgoraPlus
    template_name = 'passerelle/contrib/agoraplus/detail.html'


class RedirectSSOView(GenericDetailView):
    model = AgoraPlus

    def get(self, request, *args, **kwargs):
        resource = self.get_object()
        if not resource.frontoffice_url:
            raise Http404('no frontoffice defined')
        url = resource.frontoffice_url + '/!portail_external.go_to_agora?'
        if (request.user.is_authenticated() and hasattr(request.user, 'saml_identifiers')
                and request.user.saml_identifiers.exists()):
            name_id = request.user.saml_identifiers.first().name_id
            link = AgoraPlusLink.objects.filter(resource=resource, name_id=name_id).first()
            if link:
                try:
                    login = resource.auth(link.login, link.password)
                    if login:
                        token = resource.get_token_raw(login)
                        url += urlencode((('p_login', login), ('oauth_token', token)))
                except AgoraAPIError as e:
                    pass
        return HttpResponseRedirect(url)


class DetailView(GenericDetailView):
    model = AgoraPlus

    name_id = None
    login = None
    family = None

    def set_user_from_request(self, request):
        '''
        Get Agora+ user from name_id, if exists, and check it.
        '''
        self.name_id = None
        self.login = None
        if 'NameID' in request.GET:
            self.name_id = request.GET['NameID']
            links = AgoraPlusLink.objects.filter(resource=self.object, name_id=self.name_id)
            if len(links) > 1:
                raise AgoraAPIError('multiple links')
            if links:
                self.login = self.object.auth(links[0].login, links[0].password)
                if not self.login:
                    raise AgoraAPIError('unable to find an Agora+ user for NameID %r' %
                                        self.name_id)
        elif 'login' in request.GET: # fallback ?login= (only for tests)
            user = self.object.get_user(request.GET['login'])
            if not user:
                raise ObjectDoesNotExist('unknown login')
            self.login = user['login']

    def get_family(self):
        if not self.family:
            self.family = self.object.get_family(name_id=self.name_id, login=self.login)
        return self.family

    def get_child(self, child_id):
        family = self.get_family()
        if not family:
            raise AgoraAPIError('empty family')
        childrens = self.get_family().get('children', [])
        for child in childrens:
            if child['id'] == child_id:
                return child
        raise AgoraAPIError('unknown child')

    def get_data(self, request, *args, **kwargs):
        raise NotImplementedError

    @utils.to_json('api')
    @utils.protected_api('can_access')
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.set_user_from_request(request)
        self.family = None
        return self.get_data(request, *args, **kwargs)


class PingView(DetailView):
    def get_data(self, request, *args, **kwargs):
        # simple check Agora+ WS availability
        token = self.object.get_token()
        response = {'ping': 'pong'}
        if 'debug' in request.GET:
            response['token'] = token
        return response


class ReferenceView(DetailView):
    def get_data(self, request, name, reference_id=None, *args, **kwargs):
        results = self.object.get_reference(name, reference_id)
        if not reference_id and 'q' in request.GET:
            q = normalize_lower(request.GET['q'])
            results = [result for result in results if q in normalize_lower(result['text'])]
        return results


class SchoolView(DetailView):
    def get_data(self, request, school_id, *args, **kwargs):
        if school_id:
            return self.object.get_school(school_id=school_id) # a specific school
        if 'birthdate' not in request.GET and 'child_id' not in request.GET:
            return self.object.get_school() # all schools
        birthdate = request.GET.get('birthdate') or self.get_child(request.GET['child_id'])['birthdate']
        year_id = request.GET.get('year_id') or None
        return self.object.get_school(family=self.get_family(), birthdate=birthdate, year_id=year_id)


class CommuneView(DetailView):
    def get_data(self, request, commune_id=None, *args, **kwargs):
        results = self.object.get_commune(commune_id)
        if not commune_id and 'q' in request.GET:
            q = normalize_lower(request.GET['q'])
            results = [result for result in results if q in normalize_lower(result['text'])]
        return results


class TypeOfStreetView(DetailView):
    def get_data(self, request, type_of_street_id=None, *args, **kwargs):
        return self.object.get_type_of_street(type_of_street_id)


class StreetView(DetailView):
    def get_data(self, request, commune_id=None, type_of_street_id=None, street_id=None, *args, **kwargs):
        results = self.object.get_street(commune_id, type_of_street_id, street_id)
        if not street_id and 'q' in request.GET:
            q = normalize_lower(request.GET['q'])
            results = [result for result in results if q in normalize_lower(result['text'])]
        return results


class EducationalStageView(DetailView):
    def get_data(self, request, edustage_id=None, *args, **kwargs):
        if edustage_id: # a specific stage
            return self.object.get_educational_stage(edustage_id=edustage_id)
        if 'birthdate' not in request.GET and 'child_id' not in request.GET:
            return self.object.get_educational_stage() # all stages
        if 'birthdate' in request.GET:
            birthdate = request.GET['birthdate']
        else:
            birthdate = self.get_child(request.GET['child_id'])['birthdate']
        return self.object.get_educational_stage(birthdate=birthdate,
                                                 year_id=request.GET.get('year_id'))


class UserView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if self.login:
            return self.object.get_user(self.login)
        else:
            return self.object.get_user() # list all users


class AuthView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if 'login' not in request.GET:
            raise AgoraAPIError('missing login parameter', http_error=400)
        if 'password' not in request.GET:
            raise AgoraAPIError('missing password parameter', http_error=400)
        login = self.object.auth(request.GET['login'], request.GET['password'])
        return bool(login)


class LinkView(GenericDetailView):
    model = AgoraPlus

    @utils.to_json('api')
    @utils.protected_api('can_access')
    def get(self, request, *args, **kwargs):
        agoraplus = self.get_object()
        if 'NameID' not in request.GET:
            raise AgoraAPIError('missing NameID parameter')
        if 'login' not in request.GET:
            raise AgoraAPIError('missing login parameter')
        if 'password' not in request.GET:
            raise AgoraAPIError('missing password parameter')
        login = request.GET['login']
        password = request.GET['password']
        login = agoraplus.auth(login, password)
        if not login:
            raise AgoraAPIError('authentication of login %r failed' % login)
        family = agoraplus.get_family(login=login)
        if not family:
            raise AgoraAPIError('no family for login %r' % login)
        name_id = request.GET['NameID']
        provision_roles(name_id)
        provision_attributes(family, login, name_id)
        AgoraPlusLink.objects.update_or_create(resource=agoraplus,
                                               name_id=name_id,
                                               defaults={'login': login, 'password': password})
        logger.info('link created: NameID:%r with Agora login:%r', name_id, login)
        return True


class UnlinkView(GenericDetailView):
    model = AgoraPlus

    @utils.to_json('api')
    @utils.protected_api('can_access')
    def get(self, request, *args, **kwargs):
        agoraplus = self.get_object()
        if 'NameID' not in request.GET:
            raise AgoraAPIError('missing NameID parameter')
        name_id = request.GET['NameID']
        provision_roles(name_id, 'DELETE')
        AgoraPlusLink.objects.filter(resource=agoraplus, name_id=name_id).delete()
        return True


class FamilyView(DetailView):
    def get_data(self, request, *args, **kwargs):
        family = self.get_family()
        if family and family.get('children'):
            self.object.add_children_plannings(family)
        return family


class FamilyItemView(DetailView):
    def get_data(self, request, key, item_id=None, *args, **kwargs):
        family = self.get_family()
        items = family.get(key, [])
        if not item_id:
            return items
        if item_id:
            for item in items:
                if item['id'] == item_id:
                    return item


class ExistEmailView(DetailView):
    def get_data(self, request, *args, **kwargs):
        email = request.GET.get('email')
        if email:
            return self.object.exist_email(email)
        return False


class ExistChildView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if 'first_name' not in request.GET:
            raise AgoraAPIError('missing first_name parameter')
        if 'last_name' not in request.GET:
            raise AgoraAPIError('missing last_name parameter')
        if 'birthdate' not in request.GET:
            raise AgoraAPIError('missing birthdate parameter')
        first_name = request.GET.get('first_name')
        last_name = request.GET.get('last_name')
        birthdate = request.GET.get('birthdate')
        return self.object.exist_child(first_name, last_name, birthdate)


class ExistRepresentantView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if 'first_name' not in request.GET:
            raise AgoraAPIError('missing first_name parameter')
        if 'last_name' not in request.GET:
            raise AgoraAPIError('missing last_name parameter')
        first_name = request.GET.get('first_name')
        last_name = request.GET.get('last_name')
        birthdate = request.GET.get('birthdate')
        return self.object.exist_representant(first_name, last_name, birthdate)


class ExistListView(DetailView):
    '''
    Returns all persons with same first_name, last_name and birthdate (opt.)
    '''
    def get_data(self, request, *args, **kwargs):
        if 'first_name' not in request.GET:
            raise AgoraAPIError('missing first_name parameter')
        if 'last_name' not in request.GET:
            raise AgoraAPIError('missing last_name parameter')
        first_name = request.GET.get('first_name')
        last_name = request.GET.get('last_name')
        birthdate = request.GET.get('birthdate')
        first_names = [first_name]
        if ' ' in first_name:
            first_names.extend(first_name.split(' '))
        last_names = [last_name]
        if ' ' in last_name:
            last_names.extend(last_name.split(' '))
        duplicates = []
        for first_name in first_names:
            for last_name in last_names:
                if self.object.exist_person(first_name, last_name, birthdate):
                    if birthdate:
                        duplicates.append('%s %s - %s' % (first_name, last_name, birthdate))
                    else:
                        duplicates.append('%s %s' % (first_name, last_name))
        return duplicates


class SasView(DetailView):
    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(SasView, self).dispatch(*args, **kwargs)

    def post_data(self, name_id, formdata):
        raise NotImplementedError

    @utils.to_json('api')
    @utils.protected_api('can_access_sas')
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.set_user_from_request(request)
        data = json.loads(request.body)
        formdata = Formdata(data)
        name_id = formdata.get('NameID')
        if not name_id:
            raise AgoraAPIError('missing NameID parameter')
        return self.post_data(name_id, formdata)


class SasFamilyView(SasView):
    def post_data(self, name_id, formdata):
        provision_roles(name_id)
        family, adult1, adult2 = formdata.get_family()
        sas_family = self.object.sas_upsert(name_id, 'FAMILY', family)
        sas_adult1 = self.object.sas_upsert(name_id, 'ADULT', adult1)
        if adult2.keys() != ['sas_id']: # not empty adult
            sas_adult2 = self.object.sas_upsert(name_id, 'ADULT', adult2)
        else:
            self.object.sas_delete(name_id, 'ADULT', adult2)
            sas_adult2 = None
        return {
            'family': sas_family,
            'adult1': sas_adult1,
            'adult2': sas_adult2,
        }


class SasFamilyPushView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        if self.login:
            raise AgoraAPIError('already linked account, cannot push')
        return self.object.push_family(self.name_id, validation='validation' in request.GET)


class SasChildView(SasView):
    def post_data(self, name_id, formdata):
        child = formdata.get_child()
        return {
            'child': self.object.sas_upsert(name_id, 'CHILD', child),
        }


class SasChildPushView(DetailView):
    def get_data(self, request, sas_child_id, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        if not self.login:
            raise AgoraAPIError('no family linked, cannot push child')
        return self.object.push_child(self.name_id, self.login, sas_child_id)


class SasChildDeleteView(DetailView):
    def get_data(self, request, sas_child_id, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        fake_sas_child = {'sas_id': 'sas_%s' % sas_child_id}
        return self.object.sas_delete(self.name_id, 'CHILD', fake_sas_child)


class SasContactView(SasView):
    def post_data(self, name_id, formdata):
        data = formdata.get_contact()
        return {
            'contact': self.object.sas_upsert(name_id, 'CONTACT', data),
        }


class SasContactPushView(DetailView):
    def get_data(self, request, sas_contact_id, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        if not self.login:
            raise AgoraAPIError('no family linked, cannot push contact')
        return self.object.push_contact(self.name_id, self.login, sas_contact_id)


class SasContactDeleteView(DetailView):
    def get_data(self, request, sas_contact_id, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        fake_sas_contact = {'sas_id': 'sas_%s' % sas_contact_id}
        return self.object.sas_delete(self.name_id, 'CONTACT', fake_sas_contact)


class SasCheckDuplicatesView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        return self.object.sas_check_duplicates(self.name_id)


class SasPurgeView(DetailView):
    def get_data(self, request, *args, **kwargs):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        provision_roles(self.name_id, method='DELETE')
        return self.object.sas_purge(self.name_id)


class PostFormdataView(DetailView):
    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(PostFormdataView, self).dispatch(*args, **kwargs)

    def post_data(self, formdata):
        raise NotImplementedError

    @utils.to_json('api')
    @utils.protected_api('can_access_sas')
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        self.set_user_from_request(request)
        data = json.loads(request.body)
        formdata = Formdata(data)
        return self.post_data(formdata)


class IncomesDeclarationView(PostFormdataView):
    def post_data(self, formdata):
        if not self.name_id:
            raise AgoraAPIError('missing NameID parameter')
        if not self.login:
            raise AgoraAPIError('no family linked, cannot add incomes declaration')
        data = formdata.get_incomes_declaration()
        return self.object.incomes_declaration(self.login, data)


class SchoolEnrollmentView(PostFormdataView):
    def post_data(self, formdata):
        data = formdata.get_school_enrollment()
        return self.object.school_enrollment(data)


class SchoolEnrollmentsView(DetailView):
    def get_data(self, request, *args, **kwargs):
        family = self.object.get_agoraplus_family(login=self.login, raise_error=True)
        enrollments = []
        for child in family.get('children') or []:
            enrollment = self.object.get_school_enrollment(child)
            enrollments.extend(enrollment)
        return enrollments


class NurseryEnrollmentView(PostFormdataView):
    def post_data(self, formdata):
        data = formdata.get_nursery_enrollment()
        return self.object.nursery_enrollment(data)


class NurseryEnrollmentResultView(DetailView):
    def get_data(self, request, enroll_id, *args, **kwargs):
        return self.object.get_nursery_enrollment_result(enroll_id)


class InvoicesView(DetailView):
    def get_data(self, request, **kwargs):
        if not self.login: # unlinked account: no invoice
            return []
        return self.object.get_payable_invoices(self.login)


class HistoryInvoicesView(DetailView):
    def get_data(self, request,  **kwargs):
        if not self.login: # unlinked account: no invoice
            return []
        return self.object.get_historical_invoices(self.login)


class InvoicePayView(DetailView):
    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(InvoicePayView, self).dispatch(*args, **kwargs)

    @utils.to_json('api')
    @utils.protected_api('can_access')
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        data = json.loads(request.body)
        return self.object.pay_invoice(kwargs['invoice_id'], data['transaction_id'],
                            data['transaction_date'])


class InvoiceView(DetailView):
    def get_data(self, request, invoice_id, **kwargs):
        if not self.login: # unlinked account: no invoice
            return None
        return self.object.get_invoice(self.login, invoice_id)


class InvoicePDFView(DetailView):
    def get_data(self, request, invoice_id, **kwargs):
        pdf = self.object.get_invoice_pdf(self.login, invoice_id)
        response = HttpResponse(content_type='application/pdf')
        response['Content-Disposition'] = 'attachment; filename="%s.pdf"' % invoice_id
        response.write(pdf)
        return response


class DocumentView(DetailView):
    @method_decorator(csrf_exempt)
    def dispatch(self, *args, **kwargs):
        return super(DocumentView, self).dispatch(*args, **kwargs)

    @utils.protected_api('can_access')
    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        try:
            data = json.loads(request.body)
            uri = data['uri']
            content_type = data['content_type']
        except (ValueError, TypeError, KeyError) as e:
            return HttpResponseBadRequest()
        document = self.object.get_document(uri, content_type)
        response = HttpResponse(content_type=content_type)
        if document is not None:
            response.write(document)
        else:
            response.status_code = 404
        return response


class AddressUpdateView(PostFormdataView):
    def post_data(self, formdata):
        new_address = formdata.get_address()
        leaving_date = formdata.get('leaving_date')
        family = self.object.get_agoraplus_family(login=self.login)
        if not family:
            raise Http404(_('no family in Agora+'))
        return self.object.update_address(family, new_address, leaving_date)


class PhoneUpdateView(PostFormdataView):
    def post_data(self, formdata):
        adult_id = formdata.get('adult').get('id')
        new_phone_number = formdata.get('phone')
        new_cellphone_number = formdata.get('cellphone')
        try:
            return self.object.update_phone_numbers(self.login, self.name_id, adult_id,
                            new_phone_number, new_cellphone_number)
        except ObjectDoesNotExist, e:
            raise Http404(e.message)


class ProfessionUpdateView(PostFormdataView):
    def post_data(self, formdata):
        adult_id = formdata.get('adult').get('id')
        new_profession = formdata.get('profession')
        new_pcs = formdata.get('pcs')
        new_employer_name = formdata.get('employer')
        new_employer_city = formdata.get('employer_city')
        new_employer_phone = formdata.get('employer_phone')
        try:
            return self.object.update_profession(self.login, self.name_id, adult_id,
                    new_profession, new_pcs, new_employer_name,
                    new_employer_city, new_employer_phone)
        except ObjectDoesNotExist, e:
            raise Http404(e.message)
