"""
Views for the dashboard app.
Different dashboards are rendered based on user role.
"""
from django.views.generic import TemplateView, ListView, CreateView, UpdateView
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import redirect, get_object_or_404, render
from django.contrib import messages
from django.urls import reverse_lazy

from utils.permissions import (
    AdminRequiredMixin, TeacherRequiredMixin, StudentRequiredMixin,
    SupportStaffRequiredMixin, AccountsStaffRequiredMixin,
)


class DashboardRedirectView(LoginRequiredMixin, TemplateView):
    """Redirect users to their role-specific dashboard."""

    def get(self, request, *args, **kwargs):
        role = request.user.role
        if role == 'admin':
            return redirect('dashboard:admin')
        elif role == 'support_staff':
            return redirect('dashboard:support_staff')
        elif role == 'accounts_staff':
            return redirect('dashboard:accounts_staff')
        elif role == 'teacher':
            return redirect('dashboard:teacher')
        elif role == 'student':
            return redirect('dashboard:student')
        return redirect('home')


class AdminDashboardView(AdminRequiredMixin, TemplateView):
    """Dashboard for admin users."""
    template_name = 'dashboard/admin_dashboard.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from apps.accounts.models import CustomUser
        from apps.courses.models import Course
        from apps.enrollments.models import Enrollment

        context['total_students'] = CustomUser.objects.filter(role='student').count()
        context['total_teachers'] = CustomUser.objects.filter(role='teacher').count()
        context['total_support_staff'] = CustomUser.objects.filter(role='support_staff').count()
        context['total_accounts_staff'] = CustomUser.objects.filter(role='accounts_staff').count()
        context['total_courses'] = Course.objects.count()
        context['published_courses'] = Course.objects.filter(status='published').count()
        context['pending_enrollments'] = Enrollment.objects.filter(status='pending').count()
        context['total_enrollments'] = Enrollment.objects.count()
        context['recent_enrollments'] = Enrollment.objects.select_related('student', 'course').order_by('-requested_at')[:10]
        context['recent_users'] = CustomUser.objects.order_by('-date_joined')[:5]
        return context


class SupportStaffDashboardView(SupportStaffRequiredMixin, TemplateView):
    """Dashboard for support staff — tickets, schedules, student help (no fees)."""
    template_name = 'dashboard/support_staff_dashboard.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from apps.support.models import SupportTicket

        context['open_tickets'] = SupportTicket.objects.filter(status='open').count()
        context['in_progress_tickets'] = SupportTicket.objects.filter(status='in_progress').count()
        context['recent_tickets'] = SupportTicket.objects.select_related('created_by').order_by('-updated_at')[:8]
        return context


class AccountsStaffDashboardView(AccountsStaffRequiredMixin, TemplateView):
    """Dashboard for accounts staff — fees and payment review only."""
    template_name = 'dashboard/accounts_staff_dashboard.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from django.db.models import Q
        from apps.enrollments.models import Enrollment
        from apps.certifications.models import (
            APP_PENDING_PAYMENT,
            APP_PENDING_REATTEMPT,
            CertificationApplication,
            Certificate,
        )

        pending = Enrollment.objects.filter(status='pending').select_related('student', 'course')
        cert_pending = CertificationApplication.objects.filter(
            status__in=[APP_PENDING_PAYMENT, APP_PENDING_REATTEMPT]
        ).filter(Q(payment_proof__isnull=False) & ~Q(payment_proof='')).select_related(
            'candidate', 'program'
        )

        context['pending_enrollments'] = pending.count()
        context['pending_cert_payments'] = cert_pending.count()
        context['approved_enrollments'] = Enrollment.objects.filter(status='approved').count()
        context['certified_count'] = Certificate.objects.count()
        context['recent_pending'] = pending.order_by('-requested_at')[:8]
        context['recent_cert_pending'] = cert_pending.order_by('-updated_at')[:8]
        return context


class TeacherDashboardView(TeacherRequiredMixin, TemplateView):
    """Dashboard for teacher users."""
    template_name = 'dashboard/teacher_dashboard.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from apps.courses.models import Course
        from apps.enrollments.models import Enrollment
        from apps.certifications.models import CertificationProgram

        teacher = self.request.user
        teacher_courses = Course.objects.filter(teachers=teacher)
        cert_programs = CertificationProgram.objects.filter(teachers=teacher)

        context['my_courses'] = teacher_courses.order_by('-created_at')
        context['total_courses'] = teacher_courses.count()
        context['published_courses'] = teacher_courses.filter(status='published').count()
        context['draft_courses'] = teacher_courses.filter(status='draft').count()
        context['total_students'] = Enrollment.objects.filter(
            course__in=teacher_courses, status='approved'
        ).values('student').distinct().count()
        context['awaiting_admission'] = Enrollment.objects.filter(
            course__in=teacher_courses, status='pending'
        ).count()
        context['assigned_cert_programs'] = cert_programs.count()
        return context


class TeacherManageCoursesView(TeacherRequiredMixin, TemplateView):
    """Course management page for teachers — shows only their own courses."""
    template_name = 'dashboard/teacher_manage_courses.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from apps.courses.models import Course
        from apps.enrollments.models import Enrollment

        teacher = self.request.user
        status_filter = self.request.GET.get('status', '')
        courses = Course.objects.filter(teachers=teacher).prefetch_related('modules__lessons')
        if status_filter in ('published', 'draft'):
            courses = courses.filter(status=status_filter)
        courses = courses.order_by('-created_at')

        context['courses'] = courses
        context['total'] = Course.objects.filter(teachers=teacher).count()
        context['published'] = Course.objects.filter(teachers=teacher, status='published').count()
        context['drafts'] = Course.objects.filter(teachers=teacher, status='draft').count()
        context['total_modules'] = sum(c.modules.count() for c in Course.objects.filter(teachers=teacher).prefetch_related('modules'))
        context['status_filter'] = status_filter
        return context


class StudentDashboardView(StudentRequiredMixin, TemplateView):
    """Dashboard for student users."""
    template_name = 'dashboard/student_dashboard.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from apps.enrollments.models import Enrollment

        student = self.request.user
        enrollments = Enrollment.objects.filter(student=student).select_related('course')

        context['enrollments'] = enrollments
        context['approved_count'] = enrollments.filter(status='approved').count()
        context['pending_count'] = enrollments.filter(status='pending').count()
        context['rejected_count'] = enrollments.filter(status='rejected').count()
        context['approved_courses'] = enrollments.filter(status='approved')
        return context


class ManageUsersView(AdminRequiredMixin, ListView):
    """Admin view to manage all platform users."""
    template_name = 'dashboard/manage_users.html'
    context_object_name = 'users'

    def get_queryset(self):
        from apps.accounts.models import CustomUser
        queryset = CustomUser.objects.all().order_by('role', 'username')
        role_filter = self.request.GET.get('role', '')
        if role_filter:
            queryset = queryset.filter(role=role_filter)
        return queryset

    def get_context_data(self, **kwargs):
        from apps.accounts.models import CustomUser
        context = super().get_context_data(**kwargs)
        context['role_filter'] = self.request.GET.get('role', '')
        context['total_users'] = CustomUser.objects.count()
        context['student_count'] = CustomUser.objects.filter(role='student').count()
        context['teacher_count'] = CustomUser.objects.filter(role='teacher').count()
        context['admin_count'] = CustomUser.objects.filter(role='admin').count()
        context['support_staff_count'] = CustomUser.objects.filter(role='support_staff').count()
        return context


class ManageCoursesAdminView(AdminRequiredMixin, ListView):
    """Admin view to manage all courses with module details."""
    template_name = 'dashboard/manage_courses.html'
    context_object_name = 'courses'

    def get_queryset(self):
        from apps.courses.models import Course
        return Course.objects.prefetch_related('teachers', 'modules__lessons').order_by('-created_at')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        from apps.courses.models import Course, Module
        context['total_courses'] = Course.objects.count()
        context['published_count'] = Course.objects.filter(status='published').count()
        context['draft_count'] = Course.objects.filter(status='draft').count()
        context['total_modules'] = Module.objects.count()
        context['status_filter'] = self.request.GET.get('status', '')
        return context

    def get_queryset(self):
        from apps.courses.models import Course
        qs = Course.objects.prefetch_related('teachers', 'modules__lessons').order_by('-created_at')
        status_filter = self.request.GET.get('status', '')
        if status_filter:
            qs = qs.filter(status=status_filter)
        return qs


class AdminUserCreateView(AdminRequiredMixin, CreateView):
    """Admin: create a new user of any role."""
    template_name = 'dashboard/admin_user_form.html'

    def get_form_class(self):
        from apps.accounts.forms import AdminUserCreateForm
        return AdminUserCreateForm

    def form_valid(self, form):
        user = form.save()
        try:
            from apps.dashboard.email_service import send_signup_emails
            send_signup_emails(user, request=self.request)
        except Exception:
            pass
        messages.success(self.request, f"User '{user.username}' created successfully.")
        return redirect('dashboard:manage_users')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['action'] = 'Create User'
        return context


class AdminUserDeleteView(AdminRequiredMixin, View):
    """Admin: delete a user via POST."""

    def post(self, request, pk):
        from apps.accounts.models import CustomUser
        user_to_delete = get_object_or_404(CustomUser, pk=pk)
        if user_to_delete == request.user:
            messages.error(request, "You cannot delete your own account.")
        else:
            username = user_to_delete.username
            user_to_delete.delete()
            messages.success(request, f"User '{username}' deleted successfully.")
        return redirect('dashboard:manage_users')


class AdminResetUserPasswordView(AdminRequiredMixin, View):
    """Admin: reset any user's password without knowing the old one."""
    template_name = 'dashboard/admin_reset_password.html'

    def _get_user(self, pk):
        from apps.accounts.models import CustomUser
        return get_object_or_404(CustomUser, pk=pk)

    def get(self, request, pk):
        user_obj = self._get_user(pk)
        return render(request, self.template_name, {'user_obj': user_obj})

    def post(self, request, pk):
        from django.contrib.auth.password_validation import validate_password
        from django.core.exceptions import ValidationError as DjangoValidationError
        user_obj = self._get_user(pk)
        p1 = request.POST.get('new_password1', '').strip()
        p2 = request.POST.get('new_password2', '').strip()
        errors = []
        if not p1:
            errors.append('Password cannot be empty.')
        elif p1 != p2:
            errors.append('Passwords do not match.')
        else:
            try:
                validate_password(p1, user_obj)
            except DjangoValidationError as e:
                errors.extend(e.messages)
        if errors:
            return render(request, self.template_name, {
                'user_obj': user_obj,
                'errors': errors,
            })
        user_obj.set_password(p1)
        user_obj.save()
        messages.success(request, f"Password for '{user_obj.username}' has been reset successfully.")
        return redirect('dashboard:manage_users')


class AdminCourseCreateView(AdminRequiredMixin, CreateView):
    """Admin: create a new course."""
    template_name = 'dashboard/admin_course_form.html'

    def get_form_class(self):
        from apps.courses.forms import AdminCourseForm
        return AdminCourseForm

    def form_valid(self, form):
        course = form.save()
        messages.success(self.request, f"Course '{course.title}' created successfully.")
        return redirect('dashboard:manage_courses')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['action'] = 'Create Course'
        return context


class AdminCourseEditView(AdminRequiredMixin, UpdateView):
    """Admin: edit an existing course."""
    template_name = 'dashboard/admin_course_form.html'

    def get_object(self, queryset=None):
        from apps.courses.models import Course
        return get_object_or_404(Course, pk=self.kwargs['pk'])

    def get_form_class(self):
        from apps.courses.forms import AdminCourseForm
        return AdminCourseForm

    def form_valid(self, form):
        course = form.save()
        messages.success(self.request, f"Course '{course.title}' updated successfully.")
        return redirect('dashboard:manage_courses')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['action'] = 'Edit Course'
        return context


class AdminCourseDeleteView(AdminRequiredMixin, View):
    """Admin: delete a course via POST."""

    def post(self, request, pk):
        from apps.courses.models import Course
        course = get_object_or_404(Course, pk=pk)
        title = course.title
        course.delete()
        messages.success(request, f"Course '{title}' deleted successfully.")
        return redirect('dashboard:manage_courses')


class AdminModuleCreateView(AdminRequiredMixin, CreateView):
    """Admin: add a module to a course."""
    template_name = 'dashboard/admin_module_form.html'

    def get_form_class(self):
        from apps.courses.forms import ModuleForm
        return ModuleForm

    def dispatch(self, request, *args, **kwargs):
        from apps.courses.models import Course
        self.course = get_object_or_404(Course, pk=kwargs['course_pk'])
        return super().dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        module = form.save(commit=False)
        module.course = self.course
        module.save()
        messages.success(self.request, f"Module '{module.title}' added successfully.")
        return redirect('dashboard:manage_courses')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['course'] = self.course
        context['action'] = 'Add Module'
        return context


class AdminModuleEditView(AdminRequiredMixin, UpdateView):
    """Admin: edit an existing module."""
    template_name = 'dashboard/admin_module_form.html'

    def get_object(self, queryset=None):
        from apps.courses.models import Module
        return get_object_or_404(Module, pk=self.kwargs['pk'])

    def get_form_class(self):
        from apps.courses.forms import ModuleForm
        return ModuleForm

    def form_valid(self, form):
        module = form.save()
        messages.success(self.request, f"Module '{module.title}' updated successfully.")
        return redirect('dashboard:manage_courses')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['course'] = self.object.course
        context['action'] = 'Edit Module'
        return context


class AdminModuleDeleteView(AdminRequiredMixin, View):
    """Admin: delete a module via POST."""

    def post(self, request, pk):
        from apps.courses.models import Module
        module = get_object_or_404(Module, pk=pk)
        course_title = module.course.title
        title = module.title
        module.delete()
        messages.success(request, f"Module '{title}' from '{course_title}' deleted successfully.")
        return redirect('dashboard:manage_courses')


class AdminSettingsView(AdminRequiredMixin, View):
    """Admin: academy branding, currency, and payment method settings."""
    template_name = 'dashboard/manage_settings.html'

    def get(self, request):
        from apps.dashboard.models import SiteSettings
        from apps.dashboard.forms import SiteSettingsForm
        settings_obj = SiteSettings.load()
        form = SiteSettingsForm(instance=settings_obj)
        return render(request, self.template_name, {
            'form': form,
            'settings_obj': settings_obj,
            'webhook_url': request.build_absolute_uri('/enrollments/webhooks/stripe/'),
        })

    def post(self, request):
        from apps.dashboard.models import SiteSettings
        from apps.dashboard.forms import SiteSettingsForm
        settings_obj = SiteSettings.load()
        form = SiteSettingsForm(request.POST, request.FILES, instance=settings_obj)
        if form.is_valid():
            form.save()
            messages.success(request, 'Academy settings saved successfully.')
            return redirect('dashboard:manage_settings')
        messages.error(request, 'Please fix the errors below.')
        return render(request, self.template_name, {
            'form': form,
            'settings_obj': settings_obj,
            'webhook_url': request.build_absolute_uri('/enrollments/webhooks/stripe/'),
        })


class ManageRolesView(AdminRequiredMixin, TemplateView):
    """Admin: define and review platform roles and their permissions."""
    template_name = 'dashboard/manage_roles.html'

    def get_context_data(self, **kwargs):
        from apps.accounts.models import CustomUser
        from utils.choices import ROLE_CHOICES, ROLE_PERMISSIONS
        context = super().get_context_data(**kwargs)
        roles = []
        for code, label in ROLE_CHOICES:
            roles.append({
                'code': code,
                'label': label,
                'count': CustomUser.objects.filter(role=code).count(),
                'permissions': ROLE_PERMISSIONS.get(code, []),
            })
        context['roles'] = roles
        context['total_users'] = CustomUser.objects.count()
        return context


class AdminUserEditView(AdminRequiredMixin, UpdateView):
    """Admin: edit user role and profile fields."""
    template_name = 'dashboard/admin_user_edit.html'

    def get_object(self, queryset=None):
        from apps.accounts.models import CustomUser
        return get_object_or_404(CustomUser, pk=self.kwargs['pk'])

    def get_form_class(self):
        from apps.accounts.forms import AdminUserUpdateForm
        return AdminUserUpdateForm

    def form_valid(self, form):
        user = form.save()
        # Keep Django staff/superuser flags aligned with admin role
        if user.role == 'admin':
            user.is_staff = True
            user.is_superuser = True
            user.save(update_fields=['is_staff', 'is_superuser'])
        else:
            user.is_superuser = False
            user.save(update_fields=['is_superuser'])
        messages.success(
            self.request,
            f"User '{user.username}' updated — role set to {user.get_role_display()}."
        )
        return redirect('dashboard:manage_users')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['action'] = 'Edit User & Role'
        context['user_obj'] = self.object
        return context


# ── Categories ───────────────────────────────────────────────────────────────

class ManageCategoriesView(AdminRequiredMixin, ListView):
    template_name = 'dashboard/manage_categories.html'
    context_object_name = 'categories'

    def get_queryset(self):
        from apps.courses.models import Category
        return Category.objects.all().order_by('order', 'name')


class CategoryCreateView(AdminRequiredMixin, CreateView):
    template_name = 'dashboard/category_form.html'
    success_url = reverse_lazy('dashboard:manage_categories')

    def get_form_class(self):
        from apps.courses.forms import CategoryForm
        return CategoryForm

    def form_valid(self, form):
        cat = form.save()
        messages.success(self.request, f"Category '{cat.name}' created.")
        return redirect(self.success_url)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['action'] = 'Add Category'
        return ctx


class CategoryEditView(AdminRequiredMixin, UpdateView):
    template_name = 'dashboard/category_form.html'
    success_url = reverse_lazy('dashboard:manage_categories')
    pk_url_kwarg = 'pk'

    def get_queryset(self):
        from apps.courses.models import Category
        return Category.objects.all()

    def get_form_class(self):
        from apps.courses.forms import CategoryForm
        return CategoryForm

    def form_valid(self, form):
        cat = form.save()
        messages.success(self.request, f"Category '{cat.name}' updated.")
        return redirect(self.success_url)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['action'] = 'Edit Category'
        return ctx


class CategoryDeleteView(AdminRequiredMixin, View):
    def post(self, request, pk):
        from apps.courses.models import Category
        cat = get_object_or_404(Category, pk=pk)
        name = cat.name
        cat.delete()
        messages.success(request, f"Category '{name}' deleted.")
        return redirect('dashboard:manage_categories')


class AdminGuideView(AdminRequiredMixin, View):
    """Serve the standalone HTML admin how-to guide."""

    def get(self, request):
        from django.conf import settings
        from django.http import FileResponse, Http404
        path = settings.BASE_DIR / 'static' / 'docs' / 'admin-guide.html'
        if not path.exists():
            raise Http404('Admin guide not found.')
        return FileResponse(open(path, 'rb'), content_type='text/html; charset=utf-8')


# ── Email / SMTP ─────────────────────────────────────────────────────────────

class AdminSmtpTestView(AdminRequiredMixin, View):
    """Send a one-off test email using current SMTP settings."""

    def post(self, request):
        from apps.dashboard.forms import SmtpTestForm
        from apps.dashboard.email_service import send_test_email

        form = SmtpTestForm(request.POST)
        if not form.is_valid():
            messages.error(request, 'Enter a valid email address to send a test.')
            return redirect('dashboard:manage_settings')
        to_email = form.cleaned_data['test_email']
        try:
            send_test_email(to_email)
            messages.success(request, f'Test email sent to {to_email}. Check the inbox (and spam).')
        except Exception as exc:
            messages.error(request, f'Test email failed: {exc}')
        from django.urls import reverse
        return redirect(reverse('dashboard:manage_settings') + '#email')


class EmailTemplateListView(AdminRequiredMixin, ListView):
    template_name = 'dashboard/email_templates.html'
    context_object_name = 'templates'

    def get_queryset(self):
        from apps.dashboard.models import EmailTemplate
        return EmailTemplate.objects.all().order_by('trigger', 'name')


class EmailTemplateCreateView(AdminRequiredMixin, CreateView):
    template_name = 'dashboard/email_template_form.html'
    success_url = reverse_lazy('dashboard:email_templates')

    def get_form_class(self):
        from apps.dashboard.forms import EmailTemplateForm
        return EmailTemplateForm

    def form_valid(self, form):
        obj = form.save()
        messages.success(self.request, f"Email template '{obj.name}' created.")
        return redirect(self.success_url)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['action'] = 'Create email template'
        return ctx


class EmailTemplateEditView(AdminRequiredMixin, UpdateView):
    template_name = 'dashboard/email_template_form.html'
    success_url = reverse_lazy('dashboard:email_templates')
    pk_url_kwarg = 'pk'

    def get_queryset(self):
        from apps.dashboard.models import EmailTemplate
        return EmailTemplate.objects.all()

    def get_form_class(self):
        from apps.dashboard.forms import EmailTemplateForm
        return EmailTemplateForm

    def form_valid(self, form):
        obj = form.save()
        messages.success(self.request, f"Email template '{obj.name}' updated.")
        return redirect(self.success_url)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['action'] = 'Edit email template'
        return ctx


class EmailTemplateDeleteView(AdminRequiredMixin, View):
    def post(self, request, pk):
        from apps.dashboard.models import EmailTemplate
        tmpl = get_object_or_404(EmailTemplate, pk=pk)
        name = tmpl.name
        tmpl.delete()
        messages.success(request, f"Email template '{name}' deleted.")
        return redirect('dashboard:email_templates')


class AdminComposeEmailView(AdminRequiredMixin, View):
    """Compose and send email to one user, a role, or a custom list (bulk)."""
    template_name = 'dashboard/compose_email.html'

    def get(self, request):
        from apps.dashboard.forms import ComposeEmailForm
        from apps.dashboard.models import EmailTemplate
        import json

        form = ComposeEmailForm()
        prefill = {}
        for tmpl in EmailTemplate.objects.filter(is_active=True):
            prefill[str(tmpl.pk)] = {
                'subject': tmpl.subject,
                'body_html': tmpl.body_html,
                'body_text': tmpl.body_text,
            }
        return render(request, self.template_name, {
            'form': form,
            'template_prefill_json': json.dumps(prefill),
        })

    def post(self, request):
        from apps.accounts.models import CustomUser
        from apps.dashboard.forms import ComposeEmailForm
        from apps.dashboard.email_service import send_bulk_personalized, send_email, build_context, render_placeholders
        from apps.dashboard.models import EmailTemplate
        import json

        form = ComposeEmailForm(request.POST)
        prefill = {}
        for tmpl in EmailTemplate.objects.filter(is_active=True):
            prefill[str(tmpl.pk)] = {
                'subject': tmpl.subject,
                'body_html': tmpl.body_html,
                'body_text': tmpl.body_text,
            }
        if not form.is_valid():
            return render(request, self.template_name, {
                'form': form,
                'template_prefill_json': json.dumps(prefill),
            })

        data = form.cleaned_data
        subject = data['subject']
        html_body = data['body_html']
        text_body = data.get('body_text') or ''
        mode = data['recipient_mode']

        try:
            if mode == 'emails':
                # Same content to arbitrary addresses (no user personalization beyond blank name)
                ok = fail = 0
                for addr in data['parsed_emails']:
                    ctx = build_context(request=request)
                    ctx['email'] = addr
                    try:
                        n = send_email(
                            to=[addr],
                            subject=render_placeholders(subject, ctx),
                            text_body=render_placeholders(text_body, ctx),
                            html_body=render_placeholders(html_body, ctx),
                            fail_silently=False,
                        )
                        ok += 1 if n else 0
                        fail += 0 if n else 1
                    except Exception:
                        fail += 1
                messages.success(
                    request,
                    f'Bulk send finished: {ok} sent, {fail} failed.',
                )
            else:
                if mode == 'role':
                    users = CustomUser.objects.filter(role=data['role']).exclude(email='')
                else:
                    users = data['user_ids']
                ok, fail = send_bulk_personalized(
                    users=users,
                    subject=subject,
                    html_body=html_body,
                    text_body=text_body,
                    request=request,
                    fail_silently=True,
                )
                messages.success(
                    request,
                    f'Bulk send finished: {ok} sent, {fail} failed.',
                )
        except Exception as exc:
            messages.error(request, f'Email send failed: {exc}')
            return render(request, self.template_name, {
                'form': form,
                'template_prefill_json': json.dumps(prefill),
            })

        return redirect('dashboard:compose_email')
