"""Tests for B-2.3 — DictIA-branded French transactional emails (verification + reset).

Covers:
  - _get_email_template uses DictIA branding (no "Speakr" leaks).
  - send_verification_email subject/body in French + DictIA.
  - send_password_reset_email subject/body in French + DictIA.
  - User display name (user.name) used in greetings, fallback to username.
  - Anti-enumeration: /forgot-password gives the same flash for known/unknown emails.
  - Cooldowns are enforced (60s) for resend-verification.
  - SMTP_FROM_NAME defaults to "DictIA" when env var unset.
  - send_verification_email returns False (no exception) when SMTP misconfigured.
  - check_email.html refondu — extends marketing/base.html (DictIA brand tokens, no
    legacy `var(--text-primary)` styles).

Note: pytest cannot collect this file on Windows native because src/init_db.py
imports `fcntl` (POSIX-only). Tests run in CI / Docker. A manual driver may be
provided alongside this file for Windows verification.
"""
import os
import sys
from datetime import datetime, timedelta
from unittest.mock import patch

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
os.environ.setdefault('SECRET_KEY', 'test-secret-key')

from src.app import app, db  # noqa: E402
from src.models.user import User  # noqa: E402


# --- Helpers ----------------------------------------------------------------

def _set_smtp_env():
    os.environ['ENABLE_EMAIL_VERIFICATION'] = 'true'
    os.environ['SMTP_HOST'] = 'smtp.test'
    os.environ['SMTP_USERNAME'] = 'u'
    os.environ['SMTP_PASSWORD'] = 'p'


def _clear_smtp_env():
    for k in ('ENABLE_EMAIL_VERIFICATION', 'REQUIRE_EMAIL_VERIFICATION',
              'SMTP_HOST', 'SMTP_USERNAME', 'SMTP_PASSWORD',
              'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS'):
        os.environ.pop(k, None)


def _make_user(username='jane', email='jane@x.qc.ca', name='Jane Bouchard'):
    user = User(username=username, email=email, password='x' * 60,
                name=name, email_verified=False)
    db.session.add(user)
    db.session.commit()
    return user


# --- Tests ------------------------------------------------------------------

def test_email_template_uses_dictia_branding():
    """_get_email_template wraps content in DictIA-branded HTML scaffold (no Speakr)."""
    with app.app_context():
        from src.services.email import _get_email_template
        html, text = _get_email_template(
            content_html='<p>hello</p>',
            content_text='hello',
            subject='Test',
        )
        assert 'DictIA' in html, 'HTML must contain DictIA brand'
        assert 'DictIA' in text, 'Plain text must contain DictIA brand'
        assert 'Speakr' not in html, 'No "Speakr" string must remain in template'
        assert 'Speakr' not in text, 'No "Speakr" string must remain in plain text'
        # French footer copy + canonical contact email
        assert 'info@dictia.ca' in html
        assert 'Loi' in html and '25' in html, 'Tagline must mention Loi 25'


def test_email_template_header_uses_brand_gradient():
    """Header bg must use the official DictIA brand gradient (blue → cyan → fuchsia,
    matches the official logo). The legacy #0062ff/#00bdd8/#00c896 palette must be gone."""
    with app.app_context():
        from src.services.email import _get_email_template
        html, _ = _get_email_template('x', 'x', 'Test')
        # Legacy palette must be removed
        assert '#0062ff' not in html, 'Legacy header color #0062ff must be removed'
        assert '#00bdd8' not in html, 'Legacy mid color #00bdd8 must be removed'
        assert '#00c896' not in html, 'Legacy end color #00c896 must be removed'
        # New official-logo palette must be present
        assert '#2563eb' in html, 'DictIA brand blue (#2563eb) must be present'
        assert '#06b6d4' in html, 'DictIA brand cyan (#06b6d4) must be present'
        assert '#c026d3' in html, 'DictIA brand fuchsia (#c026d3) must be present'


def test_verification_email_subject_is_french_with_dictia():
    """Subject = 'Vérifiez votre courriel — DictIA'."""
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            user = _make_user()
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_verification_email
                send_verification_email(user)
                args, _ = mock_send.call_args
                _to, subject, _html, _text = args
                assert subject == 'Vérifiez votre courriel — DictIA'
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_verification_email_body_uses_user_name_when_set():
    """Greeting uses user.name (display name) when populated."""
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            user = _make_user(username='jane123', email='jane@x.qc.ca',
                              name='Jane Bouchard')
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_verification_email
                send_verification_email(user)
                args, _ = mock_send.call_args
                _to, _subject, html, text = args
                assert 'Bonjour Jane Bouchard' in html
                assert 'Bonjour Jane Bouchard' in text
                assert 'Bonjour jane123' not in html
                # French body copy
                assert 'Vérifier mon courriel' in html
                assert 'Bienvenue chez DictIA' in html or "Bienvenue chez DictIA" in text
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_verification_email_body_falls_back_to_username():
    """When user.name is None, greeting uses user.username."""
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            user = _make_user(username='bob42', email='bob@x.qc.ca', name=None)
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_verification_email
                send_verification_email(user)
                args, _ = mock_send.call_args
                _to, _subject, html, _text = args
                assert 'Bonjour bob42' in html
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_password_reset_subject_french():
    """Subject = 'Réinitialiser votre mot de passe — DictIA'."""
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            user = _make_user(username='carol', email='carol@x.qc.ca',
                              name='Carol Tremblay')
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_password_reset_email
                send_password_reset_email(user)
                args, _ = mock_send.call_args
                _to, subject, html, _text = args
                assert subject == 'Réinitialiser votre mot de passe — DictIA'
                assert 'Bonjour Carol Tremblay' in html
                assert 'Réinitialiser mon mot de passe' in html
                assert 'Speakr' not in html
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_send_verification_returns_false_when_smtp_not_configured():
    """No exception, just False — keeps registration robust."""
    with app.app_context():
        _clear_smtp_env()
        # Verification enabled but SMTP missing
        os.environ['ENABLE_EMAIL_VERIFICATION'] = 'true'
        db.create_all()
        try:
            user = _make_user()
            from src.services.email import send_verification_email
            assert send_verification_email(user) is False
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_smtp_from_name_defaults_to_dictia():
    """When SMTP_FROM_NAME is unset, get_email_config() returns 'DictIA'."""
    _clear_smtp_env()
    from src.services.email import get_email_config
    cfg = get_email_config()
    assert cfg['from_name'] == 'DictIA', (
        'Default SMTP_FROM_NAME must be "DictIA", not "Speakr"'
    )


def test_forgot_password_returns_generic_message_for_unknown_email():
    """Anti-enumeration: unknown email gets the same generic message."""
    with app.app_context():
        _set_smtp_env()
        app.config['WTF_CSRF_ENABLED'] = False
        db.create_all()
        try:
            client = app.test_client()
            # No user exists with this email
            with patch('src.services.email._send_email', return_value=True):
                resp = client.post('/forgot-password',
                                   data={'email': 'nobody@nope.qc.ca'})
            # Page should render the generic message in body
            body = resp.data.decode('utf-8')
            assert 'Si un compte' in body or 'lien de réinitialisation' in body
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_forgot_password_returns_same_message_for_known_email():
    """Anti-enumeration: known email gets the SAME generic message."""
    with app.app_context():
        _set_smtp_env()
        app.config['WTF_CSRF_ENABLED'] = False
        db.create_all()
        try:
            user = _make_user(username='dora', email='dora@x.qc.ca')
            client = app.test_client()
            with patch('src.services.email._send_email', return_value=True):
                resp = client.post('/forgot-password',
                                   data={'email': user.email})
            body = resp.data.decode('utf-8')
            assert 'Si un compte' in body or 'lien de réinitialisation' in body
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_check_email_template_extends_marketing_base():
    """check_email.html uses DictIA marketing layout, no legacy Vue styles."""
    with app.test_request_context('/'):
        _set_smtp_env()
        app.config['WTF_CSRF_ENABLED'] = False
        db.create_all()
        try:
            from flask import render_template
            html = render_template(
                'auth/check_email.html',
                title='Vérifiez votre courriel',
                email='alice@x.qc.ca',
                action='verification',
                show_resend=True,
            )
            # New marketing layout markers
            assert 'marketing.css' in html or 'grad-text' in html or 'brand-navy' in html
            # Legacy Vue/Tailwind v3 design tokens MUST be gone
            assert 'var(--text-primary)' not in html
            assert 'var(--bg-secondary)' not in html
            # French + brand
            assert 'DictIA' in html
            assert 'alice@x.qc.ca' in html
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_verification_email_falls_back_when_name_is_whitespace():
    """Empty/whitespace name must NOT produce 'Bonjour ,' — falls back to username."""
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            user = User(username='claire42', email='claire@example.qc.ca',
                        password='x' * 60, name='   ', email_verified=False)
            db.session.add(user)
            db.session.commit()
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_verification_email
                send_verification_email(user)
                args, _ = mock_send.call_args
                _, _, html_body, text_body = args
                assert 'Bonjour ,' not in html_body
                assert 'Bonjour claire42' in html_body
                assert 'Bonjour claire42' in text_body
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_verification_email_handles_unicode_name():
    """Accented French names must round-trip through email without mojibake."""
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            user = User(username='francois', email='francois@example.qc.ca',
                        password='x' * 60, name='François Mélanie',
                        email_verified=False)
            db.session.add(user)
            db.session.commit()
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_verification_email
                send_verification_email(user)
                args, _ = mock_send.call_args
                _, _, html_body, text_body = args
                assert 'Bonjour François Mélanie' in html_body
                assert 'Bonjour François Mélanie' in text_body
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_verification_email_escapes_html_in_user_name():
    """user.name with HTML payload must be escaped in HTML body, raw in text body.

    Regression test for C1 (stored XSS). A signup with name='<img onerror=...>'
    persists the payload — without escape it executes when the verification
    email renders.
    """
    with app.test_request_context('/'):
        _set_smtp_env()
        db.create_all()
        try:
            payload = '<img src=x onerror=alert(1)>'
            user = User(username='attacker', email='attacker@x.ca',
                        password='x' * 60, name=payload, email_verified=False)
            db.session.add(user)
            db.session.commit()
            with patch('src.services.email._send_email', return_value=True) as mock_send:
                from src.services.email import send_verification_email
                send_verification_email(user)
                args, _ = mock_send.call_args
                _, _, html_body, text_body = args
                # HTML body MUST escape the payload
                assert payload not in html_body, \
                    'Raw HTML payload leaked into HTML email body!'
                assert '&lt;img src=x onerror=alert(1)&gt;' in html_body
                # Text body keeps the raw string (it's plaintext, no XSS surface)
                assert payload in text_body
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_check_email_template_escapes_email_in_response():
    """email value rendered into check_email.html must be HTML-escaped.

    Regression test for C2 (reflected XSS). Posting a script payload to
    /forgot-password reflected it unescaped via concat-then-safe pattern.
    """
    with app.app_context():
        app.config['WTF_CSRF_ENABLED'] = False
        _set_smtp_env()
        db.create_all()
        try:
            client = app.test_client()
            payload = '<script>alert(1)</script>'
            resp = client.post('/forgot-password', data={'email': payload})
            assert resp.status_code == 200
            body = resp.data.decode('utf-8')
            assert payload not in body, \
                'Raw <script> payload leaked into rendered HTML!'
            assert '&lt;script&gt;alert(1)&lt;/script&gt;' in body
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()


def test_resend_verification_rate_limited_per_user():
    """can_resend_verification returns (False, remaining) within the 60s cooldown."""
    with app.app_context():
        _set_smtp_env()
        db.create_all()
        try:
            user = _make_user(username='eric', email='eric@x.qc.ca')
            from src.services.email import can_resend_verification
            # Simulate recent send
            user.email_verification_sent_at = datetime.utcnow()
            db.session.commit()
            can, remaining = can_resend_verification(user)
            assert can is False
            assert remaining is not None and remaining > 0
            # Simulate older send (>60s) — should now allow
            user.email_verification_sent_at = datetime.utcnow() - timedelta(seconds=120)
            db.session.commit()
            can, remaining = can_resend_verification(user)
            assert can is True
            assert remaining is None
        finally:
            db.session.rollback()
            db.drop_all()
            _clear_smtp_env()
