# authentic2 - versatile identity manager
# Copyright (C) 2010-2020 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/>.

from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext_lazy as _

from authentic2.apps.journal.models import EventTypeDefinition, n_2_pairing_rev
from authentic2.apps.journal.utils import Statistics, form_to_old_new
from authentic2.custom_user.models import User, get_attributes_map

from .models import Service


class EventTypeWithService(EventTypeDefinition):
    @classmethod
    def record(cls, user=None, service=None, session=None, references=None, data=None):
        if service:
            if not data:
                data = {}
            data['service_name'] = str(service)
            if not references:
                references = []
            references = [service] + references
        super().record(user=user, session=session, references=references, data=data)

    @classmethod
    def get_service_name(self, event):
        (service,) = event.get_typed_references(Service)
        if service is not None:
            return str(service)
        if 'service_name' in event.data:
            return event.data['service_name']
        return ''


class EventTypeWithHow(EventTypeWithService):
    @classmethod
    def record(cls, user, session, service, how):
        super().record(user=user, session=session, service=service, data={'how': how})

    @classmethod
    def get_method_statistics(
        cls, group_by_time, service=None, services_ou=None, users_ou=None, start=None, end=None
    ):
        if services_ou and not service:
            service = Service.objects.filter(ou=services_ou)

        qs = cls.get_statistics(
            group_by_time=group_by_time,
            group_by_field='how',
            which_references=service,
            users_ou=users_ou,
            start=start,
            end=end,
        )
        stats = Statistics(qs, time_interval=group_by_time)

        for stat in qs:
            stats.add(x_label=stat[group_by_time], y_label=stat['how'], value=stat['count'])

        return stats.to_json(get_y_label=lambda x: _(login_method_label(x or '')))

    @classmethod
    def _get_method_statistics_by_reference(cls, group_by_time, reference, **kwargs):
        qs = cls.get_statistics(group_by_time, group_by_references=True, **kwargs)
        stats = Statistics(qs, time_interval=group_by_time)

        if reference == 'service':
            reference_labels = {service.pk: str(service) for service in Service.objects.all()}
        if reference == 'ou':
            reference_labels = {
                service.pk: str(service.ou) for service in Service.objects.all().select_related('ou')
            }

        service_ct_id = ContentType.objects.get_for_model(Service).pk
        for stat in qs:
            for reference_id in stat['reference_ids'] or []:
                content_type_id, instance_pk = n_2_pairing_rev(reference_id)
                if content_type_id == service_ct_id:
                    reference_label = reference_labels.get(instance_pk)
                    break
            else:
                reference_label = _('None')
            if reference_label:
                stats.add(x_label=stat[group_by_time], y_label=reference_label, value=stat['count'])

        return stats.to_json()

    @classmethod
    def get_service_statistics(cls, group_by_time, start=None, end=None):
        return cls._get_method_statistics_by_reference(group_by_time, 'service', start=start, end=end)

    @classmethod
    def get_service_ou_statistics(cls, group_by_time, start=None, end=None):
        return cls._get_method_statistics_by_reference(group_by_time, 'ou', start=start, end=end)


def login_method_label(how):
    if how.startswith('password'):
        return _('password')
    elif how == 'fc':
        return 'FranceConnect'
    elif how == 'saml':
        return 'SAML'
    elif how == 'oidc':
        return 'OpenIDConnect'
    elif how:
        return how
    else:
        return _('none')


def get_attributes_label(attributes_new_values):
    attributes_map = get_attributes_map()
    for name in attributes_new_values:
        if name in ('email', 'first_name', 'last_name'):
            yield str(User._meta.get_field(name).verbose_name)
        else:
            if name in attributes_map:
                yield attributes_map[name].label
            else:
                yield name


class UserLogin(EventTypeWithHow):
    name = 'user.login'
    label = _('login')

    @classmethod
    def get_message(cls, event, context):
        how = event.get_data('how')
        return _('login using {method}').format(method=login_method_label(how))


class UserLoginFailure(EventTypeWithService):
    name = 'user.login.failure'
    label = _('login failure')

    @classmethod
    def record(cls, service, username, user):
        super().record(user=user, service=service, data={'username': username})

    @classmethod
    def get_message(cls, event, context):
        username = event.get_data('username')
        return _('login failure with username "{username}"').format(username=username)


class UserRegistrationRequest(EventTypeDefinition):
    name = 'user.registration.request'
    label = _('registration request')

    @classmethod
    def record(cls, email):
        super().record(data={'email': email.lower()})

    @classmethod
    def get_message(cls, event, context):
        email = event.get_data('email')
        return _('registration request with email "%s"') % email


class UserRegistration(EventTypeWithHow):
    name = 'user.registration'
    label = _('registration')

    @classmethod
    def get_message(cls, event, context):
        how = event.get_data('how')
        return _('registration using {method}').format(method=login_method_label(how))


class UserLogout(EventTypeWithService):
    name = 'user.logout'
    label = _('logout')

    @classmethod
    def record(cls, user, session, service):
        super().record(user=user, session=session, service=service)

    @classmethod
    def get_message(cls, event, context):
        return _('logout')


class UserRequestPasswordReset(EventTypeDefinition):
    name = 'user.password.reset.request'
    label = _('password reset request')

    @classmethod
    def record(cls, user, email):
        super().record(user=user, data={'email': email.lower()})

    @classmethod
    def get_message(cls, event, context):
        email = event.get_data('email')
        if email:
            return _('password reset request with email "%s"') % email
        return super().get_message(event, context)


class UserResetPassword(EventTypeDefinition):
    name = 'user.password.reset'
    label = _('password reset')

    @classmethod
    def record(cls, user, session):
        super().record(user=user, session=session)


class UserResetPasswordFailure(EventTypeDefinition):
    name = 'user.password.reset.failure'
    label = _('password reset failure')

    @classmethod
    def record(cls, email):
        super().record(data={'email': email})

    @classmethod
    def get_message(cls, event, context):
        email = event.get_data('email')
        if email:
            return _('password reset failure with email "%s"') % email
        return super().get_message(event, context)


class UserChangePassword(EventTypeWithService):
    name = 'user.password.change'
    label = _('password change')

    @classmethod
    def record(cls, user, session, service):
        super().record(user=user, session=session, service=service)


class UserEdit(EventTypeWithService):
    name = 'user.profile.edit'
    label = _('profile edit')

    @classmethod
    def record(cls, user, session, service, form):
        data = form_to_old_new(form)
        super().record(user=user, session=session, service=service, data=data)

    @classmethod
    def get_message(cls, event, context):
        new = event.get_data('new')
        if new:
            edited_attributes = ', '.join(get_attributes_label(new))
            return _('profile edit (%s)') % edited_attributes
        return super().get_message(event, context)


class UserDeletion(EventTypeWithService):
    name = 'user.deletion'
    label = _('user deletion')

    @classmethod
    def record(cls, user, session, service):
        super().record(user=user, session=session, service=service)


class UserServiceSSO(EventTypeWithHow):
    name = 'user.service.sso'
    label = _('service single sign on')

    @classmethod
    def get_message(cls, event, context):
        service_name = cls.get_service_name(event)
        return _('service single sign on with "{service}"').format(service=service_name)


class UserServiceSSOAuthorization(EventTypeWithService):
    name = 'user.service.sso.authorization'
    label = _('consent to single sign on')

    @classmethod
    def record(cls, user, session, service, **kwargs):
        super().record(user=user, session=session, service=service, data=kwargs)

    @classmethod
    def get_message(cls, event, context):
        service_name = cls.get_service_name(event)
        return _('authorization of single sign on with "{service}"').format(service=service_name)


class UserServiceSSOUnauthorization(EventTypeWithService):
    name = 'user.service.sso.unauthorization'
    label = _('remove consent to single sign on')

    @classmethod
    def record(cls, user, session, service):
        super().record(user=user, session=session, service=service)

    @classmethod
    def get_message(cls, event, context):
        service_name = cls.get_service_name(event)
        return _('unauthorization of single sign on with "{service}"').format(service=service_name)


class UserEmailChangeRequest(EventTypeDefinition):
    name = 'user.email.change.request'
    label = _('email change request')

    @classmethod
    def record(cls, user, session, new_email):
        data = {
            'old_email': user.email,
            'email': new_email,
        }
        super().record(user=user, session=session, data=data)

    @classmethod
    def get_message(cls, event, context):
        new_email = event.get_data('email')
        return _('email change request for email address "{0}"').format(new_email)


class UserEmailChange(EventTypeDefinition):
    name = 'user.email.change'
    label = _('email change')

    @classmethod
    def record(cls, user, session, old_email, new_email):
        data = {
            'old_email': old_email,
            'email': new_email,
        }
        super().record(user=user, session=session, data=data)

    @classmethod
    def get_message(cls, event, context):
        new_email = event.get_data('email')
        old_email = event.get_data('old_email')
        return _('email address changed from "{0}" to "{1}"').format(old_email, new_email)
