"""
SMTP + templated email helpers for the academy.

Uses SiteSettings SMTP when smtp_enabled; otherwise falls back to Django settings (.env).
"""
from __future__ import annotations

import datetime
import logging
import re
from typing import Iterable, Optional

from django.conf import settings as django_settings
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template import Context, Template
from django.template.loader import render_to_string

logger = logging.getLogger(__name__)

_PLACEHOLDER_RE = re.compile(r'\{\{\s*([a-zA-Z0-9_]+)\s*\}\}')


def get_site_settings():
    from apps.dashboard.models import SiteSettings
    return SiteSettings.load()


def get_from_email(site=None) -> str:
    site = site or get_site_settings()
    custom = (site.smtp_from_email or '').strip()
    if site.smtp_enabled and custom:
        return custom
    if site.smtp_enabled and site.smtp_username:
        name = site.institute_name or 'Academy'
        return f'{name} <{site.smtp_username}>'
    return django_settings.DEFAULT_FROM_EMAIL


def get_mail_connection(site=None, fail_silently: bool = False):
    """Return a Django email connection using admin SMTP or project settings."""
    site = site or get_site_settings()
    if site.smtp_ready:
        use_ssl = bool(site.smtp_use_ssl)
        use_tls = bool(site.smtp_use_tls) and not use_ssl
        return get_connection(
            backend='django.core.mail.backends.smtp.EmailBackend',
            host=site.smtp_host.strip(),
            port=int(site.smtp_port or 587),
            username=(site.smtp_username or '').strip() or None,
            password=site.smtp_password or None,
            use_tls=use_tls,
            use_ssl=use_ssl,
            fail_silently=fail_silently,
        )
    return get_connection(fail_silently=fail_silently)


def build_context(user=None, extra: Optional[dict] = None, request=None) -> dict:
    site = get_site_settings()
    login_url = ''
    if request is not None:
        try:
            from django.urls import reverse
            login_url = request.build_absolute_uri(reverse('accounts:login'))
        except Exception:
            login_url = ''
    ctx = {
        'institute_name': site.institute_name,
        'year': datetime.date.today().year,
        'login_url': login_url,
        'reset_url': '',
        'user_name': '',
        'email': '',
        'username': '',
        'role': '',
    }
    if user is not None:
        display = ''
        if hasattr(user, 'get_display_name'):
            display = user.get_display_name() or ''
        ctx.update({
            'user': user,
            'user_name': display or getattr(user, 'username', '') or '',
            'email': getattr(user, 'email', '') or '',
            'username': getattr(user, 'username', '') or '',
            'role': getattr(user, 'get_role_display', lambda: '')() if hasattr(user, 'get_role_display') else '',
        })
    if extra:
        ctx.update(extra)
    return ctx


def render_placeholders(text: str, context: dict) -> str:
    """Render {{ var }} placeholders; also supports Django template tags if present."""
    if not text:
        return ''
    # Prefer Django Template so {% if %} works; simple {{ }} always works too.
    try:
        return Template(text).render(Context(context))
    except Exception:
        def repl(match):
            key = match.group(1)
            val = context.get(key, '')
            return '' if val is None else str(val)
        return _PLACEHOLDER_RE.sub(repl, text)


def send_email(
    *,
    to: Iterable[str],
    subject: str,
    text_body: str,
    html_body: str = '',
    fail_silently: bool = False,
) -> int:
    """Send one message to one or more recipients (same content). Returns send() count."""
    recipients = [e.strip() for e in to if e and str(e).strip()]
    if not recipients:
        return 0
    site = get_site_settings()
    connection = get_mail_connection(site=site, fail_silently=fail_silently)
    msg = EmailMultiAlternatives(
        subject=subject,
        body=text_body or (html_body and re.sub(r'<[^>]+>', '', html_body)) or '',
        from_email=get_from_email(site),
        to=recipients,
        connection=connection,
    )
    if html_body:
        msg.attach_alternative(html_body, 'text/html')
    return msg.send(fail_silently=fail_silently)


def send_templated_email(
    template,
    *,
    user=None,
    to_email: str = '',
    extra_context: Optional[dict] = None,
    request=None,
    fail_silently: bool = False,
) -> int:
    """Render an EmailTemplate and send to the user (or to_email)."""
    ctx = build_context(user=user, extra=extra_context, request=request)
    subject = render_placeholders(template.subject, ctx)
    html_body = render_placeholders(template.body_html, ctx)
    text_body = render_placeholders(template.body_text, ctx) if template.body_text else ''
    recipient = (to_email or ctx.get('email') or '').strip()
    if not recipient:
        logger.warning('send_templated_email: no recipient for template %s', template.slug)
        return 0
    return send_email(
        to=[recipient],
        subject=subject,
        text_body=text_body,
        html_body=html_body,
        fail_silently=fail_silently,
    )


def send_signup_emails(user, request=None) -> int:
    """Send all active signup-trigger templates to the new user."""
    from apps.dashboard.models import EMAIL_TRIGGER_SIGNUP, EmailTemplate

    if not getattr(user, 'email', None):
        return 0
    templates = EmailTemplate.objects.filter(
        trigger=EMAIL_TRIGGER_SIGNUP,
        is_active=True,
    )
    sent = 0
    for tmpl in templates:
        try:
            sent += send_templated_email(
                tmpl, user=user, request=request, fail_silently=True,
            )
        except Exception:
            logger.exception('Failed sending signup template %s', tmpl.slug)
    return sent


def send_password_reset_email(user, reset_url: str, expiry_hours: int, request=None) -> int:
    """
    Prefer active password_reset EmailTemplate; fall back to file templates.
    """
    from apps.dashboard.models import EMAIL_TRIGGER_PASSWORD_RESET, EmailTemplate

    site = get_site_settings()
    extra = {
        'reset_url': reset_url,
        'expiry_hours': expiry_hours,
    }
    tmpl = EmailTemplate.objects.filter(
        trigger=EMAIL_TRIGGER_PASSWORD_RESET,
        is_active=True,
    ).first()
    if tmpl:
        return send_templated_email(
            tmpl, user=user, extra_context=extra, request=request, fail_silently=False,
        )

    ctx = build_context(user=user, extra=extra, request=request)
    subject = f'Reset Your {site.institute_name} Password'
    text_body = render_to_string('accounts/email/password_reset_email.txt', ctx)
    html_body = render_to_string('accounts/email/password_reset_email.html', ctx)
    return send_email(
        to=[user.email],
        subject=subject,
        text_body=text_body,
        html_body=html_body,
        fail_silently=False,
    )


def send_bulk_personalized(
    *,
    users,
    subject: str,
    html_body: str,
    text_body: str = '',
    request=None,
    fail_silently: bool = True,
) -> tuple[int, int]:
    """
    Send the same subject/body to many users, personalizing placeholders per user.
    Returns (success_count, fail_count).
    """
    ok, fail = 0, 0
    for user in users:
        email = (getattr(user, 'email', None) or '').strip()
        if not email:
            fail += 1
            continue
        ctx = build_context(user=user, request=request)
        try:
            n = send_email(
                to=[email],
                subject=render_placeholders(subject, ctx),
                text_body=render_placeholders(text_body, ctx),
                html_body=render_placeholders(html_body, ctx),
                fail_silently=fail_silently,
            )
            if n:
                ok += 1
            else:
                fail += 1
        except Exception:
            logger.exception('Bulk email failed for %s', email)
            fail += 1
    return ok, fail


def send_test_email(to_email: str) -> None:
    """Send a short test message to verify SMTP settings."""
    site = get_site_settings()
    send_email(
        to=[to_email],
        subject=f'Test email from {site.institute_name}',
        text_body=(
            f'This is a test message from {site.institute_name}.\n'
            f'SMTP host: {site.smtp_host or "(using .env / default backend)"}\n'
            f'If you received this, email delivery is working.\n'
        ),
        html_body=(
            f'<p>This is a test message from <strong>{site.institute_name}</strong>.</p>'
            f'<p>If you received this, email delivery is working.</p>'
        ),
        fail_silently=False,
    )
