"""
View функции.
"""


from smtplib import SMTPException
from typing import Dict, Any, Optional

from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordResetForm
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.contrib.auth.models import Permission
from django.contrib.auth.tokens import default_token_generator
from django.contrib.auth.views import LoginView
from django.contrib.contenttypes.models import ContentType
from django.contrib.messages.views import SuccessMessageMixin
from django.core.handlers.wsgi import WSGIRequest
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render, redirect
from django.urls import reverse_lazy
from django.views.generic import FormView
from django_registration.views import RegistrationView

from rest_framework import viewsets
from rest_framework.response import Response

from access_controller.settings import DEFAULT_FROM_EMAIL, ZENDESK_ROLES, ZENDESK_MAX_AGENTS, ZENDESK_GROUPS
from main.extra_func import check_user_exist, update_profile, get_user_organization, \
    make_engineer, make_light_agent, get_users_list, update_users_in_model, count_users, \
    set_session_params_for_work_page, get_tickets_list_for_group
from main.forms import AdminPageUsers, CustomRegistrationForm, CustomAuthenticationForm, \
    StatisticForm, WorkGetTicketsForm
from main.serializers import ProfileSerializer, ZendeskUserSerializer
from main.zendesk_admin import zenpy
from .models import UserProfile
from .statistic_data import StatisticData


def setup_context(**kwargs) -> Dict[str, Any]:
    """
    Функция добавления в контекст статуса пользователя.

    :param profile_lit: True, при создании профиля пользователя, иначе False
    :param control_lit: False
    :param work_lit: True, при установке пользователю рабочей роли, иначе False
    :param registration_lit: True, при регистрации пользователя, иначе False
    :param login_lit: True, если пользователь залогинен, иначе False
    :param stats_lit: True, при получении пользователем прав администратора (просмотр статистики), иначе False
    :return: Контекст (context)
    """
    context = {}
    for key in ('profile_lit', 'control_lit', 'work_lit', 'registration_lit', 'login_lit', 'stats_lit'):
        if key in kwargs:
            context.update({key: True})
        else:
            context.update({key: False})
    return context


class CustomRegistrationView(RegistrationView):
    """
    Класс отображения и логики работы страницы регистрации пользователя.

    :param form_class: Форма, которую необходимо заполнить для регистрации
    :type form_class: :class:`forms.CustomRegistrationForm`
    :param template_name: Указание пути к html-странице django регистрации
    :type template_name: :class:`str`
    :param success_url: Указание пути к html-странице завершения регистрации
    :type success_url: :class:`django.utils.functional.lazy.<locals>.__proxy__`
    :param is_allowed: Определение зарегистрирован ли пользователь с введенным email на Zendesk и
                       принадлежит ли он к организации SYSTEM
    :type is_allowed: :class:`bool`
    """
    extra_context = setup_context(registration_lit=True)
    form_class = CustomRegistrationForm
    template_name = 'django_registration/registration_form.html'
    urls = {
        'done': reverse_lazy('password_reset_done'),
        'invalid_zendesk_email': reverse_lazy('django_registration_disallowed'),
        'email_sending_error': reverse_lazy('registration_email_error'),
    }
    redirect_url = 'done'

    def register(self, form: CustomRegistrationForm) -> Optional[get_user_model()]:
        """
        Функция регистрации пользователя.

        1. Ввод email пользователя, указанный на Zendesk.

        2. В случае если пользователь с данным паролем зарегистрирован на Zendesk и относится к организации SYSTEM,
        происходит сброс ссылки с установлением пароля на указанный email.

        3. Создается пользователь class User, а также его профиль.

        :param form: Email пользователя на Zendesk
        :return: User
        """
        self.redirect_url = 'done'
        if check_user_exist(form.data['email']) and get_user_organization(form.data['email']) == 'SYSTEM':
            forms = PasswordResetForm(self.request.POST)
            if forms.is_valid():
                opts = {
                    'use_https': self.request.is_secure(),
                    'token_generator': default_token_generator,
                    'from_email': DEFAULT_FROM_EMAIL,
                    'email_template_name': 'registration/password_reset_email.html',
                    'subject_template_name': 'registration/password_reset_subject.txt',
                    'request': self.request,
                    'html_email_template_name': None,
                    'extra_email_context': None,
                }
                user = get_user_model().objects.create_user(
                    username=form.data['email'],
                    email=form.data['email'],
                    password=get_user_model().objects.make_random_password(length=50)
                )
                try:
                    update_profile(user.userprofile)
                    self.set_permission(user)
                    forms.save(**opts)
                    return user
                except SMTPException:
                    self.redirect_url = 'email_sending_error'
                    return None
            else:
                self.redirect_url = 'email_sending_error'
                return None
        else:
            self.redirect_url = 'invalid_zendesk_email'
            return None

    @staticmethod
    def set_permission(user: get_user_model()) -> None:
        """
        Функция дает разрешение на просмотр страница администратора, если пользователь имеет роль admin.

        :param user: Авторизованный пользователь (получает разрешение, имея роль "admin")
        """
        if user.userprofile.role == 'admin':
            content_type = ContentType.objects.get_for_model(UserProfile)
            permission = Permission.objects.get(
                codename='has_control_access',
                content_type=content_type,
            )
            user.user_permissions.add(permission)

    def get_success_url(self, user: get_user_model() = None) -> Dict:
        """
        Функция возвращает url-адрес страницы, куда нужно перейти после успешной/не успешной регистрации.
        Используется самой django-registration.

        :param user: Пользователь, пытающийся зарегистрироваться
        :return: Адресация на страницу успешной регистрации
        """
        return self.urls[self.redirect_url]


def registration_error(request: WSGIRequest) -> HttpResponse:
    """
    Функция отображения страницы ошибки регистрации.

    :param request: Регистрация
    :return: Адресация на страницу ошибки
    """
    return render(request, 'django_registration/registration_error.html')


@login_required()
def profile_page(request: WSGIRequest) -> HttpResponse:
    """
    Функция отображения страницы профиля.

    :param request: Данные пользователя из БД
    :return: Адресация на страницу пользователя
    """
    user_profile: UserProfile = request.user.userprofile
    update_profile(user_profile)
    context = {
        'profile': user_profile,
        'pagename': 'Страница профиля',
        'ZENDESK_ROLES': ZENDESK_ROLES,
    }
    return render(request, 'pages/profile.html', context)


@login_required()
def work_page(request: WSGIRequest, required_id: int) -> HttpResponse:
    """
    Функция отображения страницы "Управления правами" для текущего пользователя (login_required).

    :param request: Объект пользователя
    :param id: id пользователя, используется для динамической адресации
    :return: Адресация на страницу "Управления правами" (либо на страницу "Авторизации", если id и user.id не совпадают
    """
    users = get_users_list()
    if request.user.id == required_id:
        if request.session.get('is_confirm', None):
            messages.success(request, 'Изменения были применены')
        elif request.session.get('is_confirm', None) is not None:
            messages.error(request, 'Изменения не были применены')
        count = request.session.get('count_tickets', None)
        if count is not None:
            messages.success(request, f'{count} тикетов назначено')
        request.session['is_confirm'] = None
        request.session['count_tickets'] = None

        engineers = []
        light_agents = []
        for user in users:
            if user.custom_role_id == ZENDESK_ROLES['engineer']:
                engineers.append(user)
            elif user.custom_role_id == ZENDESK_ROLES['light_agent']:
                light_agents.append(user)
        context = {
            'engineers': engineers,
            'agents': light_agents,
            'messages': messages.get_messages(request),
            'licences_remaining': max(0, ZENDESK_MAX_AGENTS - len(engineers)),
            'pagename': 'Управление правами',
            'get_tickets_form': WorkGetTicketsForm()
        }
        return render(request, 'pages/work.html', context)
    return redirect("login")


@login_required()
def work_hand_over(request: WSGIRequest) -> HttpResponseRedirect:
    """
    Функция позволяет текущему пользователю сдать права, а именно сменить в Zendesk роль с "engineer" на "light_agent"

    :param request: Данные текущего пользователя (login_required)
    :return: Перезагрузка текущей страницы после выполнения смены роли
    """
    make_light_agent(request.user.userprofile, request.user)
    return set_session_params_for_work_page(request)


@login_required()
def work_become_engineer(request: WSGIRequest) -> HttpResponseRedirect:
    """
    Функция позволяет текущему пользователю получить права, а именно сменить в Zendesk роль с "light_agent"
    на "engineer".

    :param request: Данные текущего пользователя (login_required)
    :return: Перезагрузка текущей страницы после выполнения смены роли
    """
    make_engineer(request.user.userprofile, request.user)
    return set_session_params_for_work_page(request)


@login_required()
def work_get_tickets(request: WSGIRequest) -> HttpResponse:
    """
    Функция получения тикетов в работу.

    :param request: Запрос на принятие тикетов в работу
    :return: Перезагрузка рабочей страницы
    """
    zenpy_user = zenpy.get_user(request.user.email)

    if request.method == 'POST':
        if zenpy_user.role == 'admin' or zenpy_user.custom_role_id == ZENDESK_ROLES['engineer']:
            form = WorkGetTicketsForm(request.POST)
            if form.is_valid():
                tickets = get_tickets_list_for_group(ZENDESK_GROUPS['buffer'])
                assigned_tickets = []
                for i in range(min(form.cleaned_data['count_tickets'], len(tickets))):
                    tickets[i].assignee = zenpy_user
                    assigned_tickets.append(tickets[i])
                zenpy.update_tickets(assigned_tickets)
                return set_session_params_for_work_page(request, len(assigned_tickets))
    return set_session_params_for_work_page(request, is_confirm=False)


def main_page(request: WSGIRequest) -> HttpResponse:
    """
    Функция переадресации на главную страницу.
    """
    return render(request, 'pages/index.html')


class AdminPageView(LoginRequiredMixin, PermissionRequiredMixin, SuccessMessageMixin, FormView):
    """
    Класс отображения страницы администратора.

    :param permission_required: Права доступа к странице администратора
    :type permission_required: :class:`str`
    :param  template_name: HTML-шаблон страницы администратора
    :type  template_name: :class:`str`
    :param form_class: Форма страницы администратора
    :type form_class: :class:`forms.AdminPageUsersForm`
    :param success_url: Адрес страницы администратора
    :type success_url: :class:`HttpResponseRedirect`
    :param success_message: Уведомление об изменении прав
    :type success_url: :class:`str`
    """
    permission_required = 'main.has_control_access'
    template_name = 'pages/adm_ruleset.html'
    form_class = AdminPageUsers
    success_url = '/control/'
    success_message = "Права были изменены."

    def form_valid(self, form: AdminPageUsers) -> AdminPageUsers:
        """
        Функция обновления страницы AdminPageUsers.

        :param form: Форма страницы администратора
        :return: Обновленная страница (пользователям проставлены новые статусы)
        """
        users = form.cleaned_data['users']
        if 'engineer' in self.request.POST:
            self.make_engineers(users)
        elif 'light_agent' in self.request.POST:
            self.make_light_agents(users)
        return super().form_valid(form)

    def make_engineers(self, users: list) -> None:
        """
        Функция проходит по списку пользователей, проставляя статус "engineer".

        :param users: Список пользователей
        :return: Обновленный список пользователей
        """
        for user in users:
            make_engineer(user, self.request.user)

    def make_light_agents(self, users: list) -> None:
        """
        Функция проходит по списку пользователей, проставляя статус "light agent".

        :param users: Список пользователей
        :return: Обновленный список пользователей
        """
        for user in users:
            make_light_agent(user, self.request.user)


class CustomLoginView(LoginView):
    """
    Класс отображения страницы авторизации пользователя.

    :param extra_context: Добавление в контекст статус пользователя "залогинен"
    :type extra_context: :class:`dict`
    :param form_class: Форма страницы авторизации
    :type form_class: :class: forms.CustomAuthenticationForm
    """
    extra_context = setup_context(login_lit=True)
    form_class = CustomAuthenticationForm


class UsersViewSet(viewsets.ReadOnlyModelViewSet):
    """
    Класс для получения пользователей с помощью api.

    :param queryset: Список пользователей с ролью 'agent'
    :type queryset: :class:`str`
    :param serializer_class: Класс сериализатор для модели профиля пользователя
    :type serializer_class: :class:`ProfileSerializer`
    """
    queryset = UserProfile.objects.filter(role='agent')
    serializer_class = ProfileSerializer

    def list(self, request: WSGIRequest, *args, **kwargs) -> Response:
        """
        Функция возвращает список пользователей Zendesk, количество engineers и light-agents.

        :param request: Запрос
        :param args: Аргументы
        :param kwargs: Параметры
        :return: Список пользователей
        """
        users = update_users_in_model()
        count = count_users(users.values)
        profiles = UserProfile.objects.filter(role='agent')
        serializer = self.get_serializer(profiles, many=True)
        res = {
            'users': serializer.data,
            'engineers': count[0],
            'light_agents': count[1],
            'zendesk_users': self.get_zendesk_users(self.choose_users(users.values, profiles)),
            'max_agents': ZENDESK_MAX_AGENTS
        }
        return Response(res)

    @staticmethod
    def choose_users(zendesk: list, model: list) -> list:
        """
        Функция формирует список пользователей, которые не зарегистрированы у нас.

        :param zendesk: Список пользователей Zendesk
        :param model: Список пользователей (модель Userprofile)
        :return: Список
        """
        users = []
        for zendesk_user in zendesk:
            if zendesk_user.name not in [user.name for user in model]:
                users.append(zendesk_user)
        return users

    @staticmethod
    def get_zendesk_users(users: list) -> list:
        """
        Функция получения списка пользователей Zendesk, не являющихся админами.

        :param users: Список пользователей
        :return: Список пользователей, не являющимися администраторами.
        """
        zendesk_users = ZendeskUserSerializer(
            data=[user for user in users if user.role != 'admin'],
            many=True
        )
        zendesk_users.is_valid()
        return zendesk_users.data


@login_required()
def statistic_page(request: WSGIRequest) -> HttpResponse:
    """
    Функция отображения страницы статистики (для "superuser").

    :param request: Данные о пользователе: email, время и интервал работы. Данные получаем через forms.StatisticForm
    :return: Адресация на страницу статистики
    """

    # if not request.user.has_perm('main.has_control_access'):
    #     raise PermissionDenied
    # context = {

    if not request.user.has_perm("main.has_control_access"):
        return redirect('index')
    context = {
        'pagename': 'страница статистики',
        'errors': list(),
    }
    if request.method == "POST":
        form = StatisticForm(request.POST)
        if form.is_valid():
            start_date, end_date = form.cleaned_data['range_start'], form.cleaned_data['range_end']
            interval, show = form.cleaned_data['interval'], form.cleaned_data['display_format']
            data = StatisticData(start_date, end_date, form.cleaned_data['email'])
            data.set_display(show)
            data.set_interval(interval)
            stats = data.get_statistic()
            if data.errors:
                context['errors'] = data.errors
            if data.warnings:
                context['warnings'] = data.warnings
            context['log_stats'] = stats if not context['errors'] else None
    elif request.method == 'GET':
        form = StatisticForm()
    context['form'] = form
    return render(request, 'pages/statistic.html', context)

def registration_failed(request: WSGIRequest) -> HttpResponse:
    """
    Функция отображения страницы "Регистрация закрыта".
    """
    return render(request, 'pages/registration_failed.html')
