"""Tests for B-2.2 — /signup Loi 25-compliant flow.

Covers:
  - GET /signup renders form with 4 unchecked consent checkboxes.
  - POST /signup rejects when CGU or politique de confidentialité is missing.
  - POST /signup creates User + 4 ConsentLog rows (one per consent type),
    capturing IP (CF-Connecting-IP preferred) and User-Agent.
  - /register is a 302 redirect to /signup (backward-compat).
  - CSRF is enforced when WTF_CSRF_ENABLED is True.

The default test config sets WTF_CSRF_ENABLED=False for convenience; tests
that need to verify CSRF temporarily flip it back to True.

Note: pytest cannot collect this file on Windows native because
src/init_db.py imports `fcntl` (POSIX-only). Tests run in CI / Docker.
"""
import os
import sys

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')
# Make sure no email sending is attempted during tests.
os.environ.setdefault('ENABLE_EMAIL_VERIFICATION', 'false')
os.environ.setdefault('REQUIRE_EMAIL_VERIFICATION', 'false')

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

# Strong password that satisfies password_check (uppercase, lowercase, digit, special, len>=8).
STRONG_PASSWORD = 'SecurePass123!Long'

VALID_FORM = {
    'email': 'jane@example.qc.ca',
    'password': STRONG_PASSWORD,
    'confirm_password': STRONG_PASSWORD,
    'first_name': 'Jane',
    'last_name': 'Bouchard',
    'cabinet': '',
    'ordre_pro': '',
}


def _disable_csrf():
    app.config['WTF_CSRF_ENABLED'] = False


def test_signup_get_returns_200_with_4_unchecked_checkboxes():
    """GET /signup renders the form with 4 consent checkboxes, none pre-checked."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            resp = client.get('/signup')
            assert resp.status_code == 200
            body = resp.data.decode('utf-8')

            # 4 distinct checkboxes (one per consent_type).
            for ctype in ('consent_cgu', 'consent_confidentialite',
                          'consent_marketing', 'consent_analytics'):
                assert f'name="{ctype}"' in body, f'missing checkbox {ctype}'

            # None pre-checked: WTForms emits checked="checked" or checked attribute
            # only when BooleanField data is truthy. With a blank GET, all 4 are False.
            for ctype in ('consent_cgu', 'consent_confidentialite',
                          'consent_marketing', 'consent_analytics'):
                # No occurrence of `name="<ctype>" ... checked` should appear.
                idx = body.find(f'name="{ctype}"')
                assert idx >= 0
                # Look at a 200-char window around the input tag.
                window = body[max(0, idx - 100): idx + 200]
                assert 'checked' not in window, (
                    f'{ctype} must NOT be pre-checked at GET time'
                )
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_rejects_without_cgu_consent():
    """POST without consent_cgu returns 400 + French flash; no User created."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            data = dict(VALID_FORM)
            # Cocher confidentialité mais PAS la CGU.
            data['consent_confidentialite'] = 'y'
            resp = client.post('/signup', data=data)
            assert resp.status_code == 400
            body = resp.data.decode('utf-8').lower()
            assert "conditions d'utilisation" in body or 'cgu' in body
            assert User.query.filter_by(email=VALID_FORM['email']).first() is None
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_rejects_without_confidentialite_consent():
    """POST without consent_confidentialite returns 400 + French flash; no User created."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            data = dict(VALID_FORM)
            data['consent_cgu'] = 'y'
            # Politique de confidentialité non cochée.
            resp = client.post('/signup', data=data)
            assert resp.status_code == 400
            body = resp.data.decode('utf-8').lower()
            assert 'confidentialit' in body
            assert User.query.filter_by(email=VALID_FORM['email']).first() is None
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_creates_4_consent_logs_with_correct_state():
    """Granular consent — 4 rows, one per type, with the correct granted/refused state."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            data = dict(VALID_FORM)
            data['consent_cgu'] = 'y'
            data['consent_confidentialite'] = 'y'
            # marketing absent => False
            data['consent_analytics'] = 'y'
            resp = client.post('/signup', data=data)
            assert resp.status_code in (200, 302)

            user = User.query.filter_by(email=VALID_FORM['email']).first()
            assert user is not None, 'User should have been created'

            consents = ConsentLog.query.filter_by(user_id=user.id).all()
            assert len(consents) == 4, (
                'One ConsentLog row per consent type — granular Loi 25 art. 14'
            )
            state = {c.consent_type: c.granted for c in consents}
            assert state == {
                'cgu': True,
                'confidentialite': True,
                'marketing': False,  # explicit refusal recorded
                'analytics': True,
            }
            assert all(c.version == '2026-04-27' for c in consents)
            assert all(c.granted_at is not None for c in consents)
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_captures_ip_and_user_agent_in_consent_logs():
    """CF-Connecting-IP is honored over remote_addr; User-Agent recorded on each row."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            data = dict(VALID_FORM)
            data['email'] = 'audit@example.qc.ca'
            data['consent_cgu'] = 'y'
            data['consent_confidentialite'] = 'y'
            resp = client.post(
                '/signup',
                data=data,
                headers={
                    'CF-Connecting-IP': '203.0.113.42',
                    'User-Agent': 'TestAgent/1.0',
                },
            )
            assert resp.status_code in (200, 302)
            user = User.query.filter_by(email='audit@example.qc.ca').first()
            assert user is not None
            consents = ConsentLog.query.filter_by(user_id=user.id).all()
            assert len(consents) == 4
            assert all(c.ip_address == '203.0.113.42' for c in consents), (
                'CF-Connecting-IP must take precedence over remote_addr'
            )
            assert all(c.user_agent == 'TestAgent/1.0' for c in consents)
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_rejects_duplicate_email():
    """Duplicate email — form re-rendered with French error; no second User row."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            existing = User(
                username='preexisting',
                email='taken@example.qc.ca',
                password='x' * 60,
            )
            db.session.add(existing)
            db.session.commit()

            client = app.test_client()
            data = dict(VALID_FORM)
            data['email'] = 'taken@example.qc.ca'
            data['consent_cgu'] = 'y'
            data['consent_confidentialite'] = 'y'
            resp = client.post('/signup', data=data)
            # Form re-rendered (200) with validation error from validate_email.
            # Pinned to 200 exactly: validate_email raises ValidationError →
            # form fails validate_on_submit() → fall-through to the final
            # `return render_template(...)` which uses the default 200.
            assert resp.status_code == 200, (
                f"Form should re-render with error inline (200), got {resp.status_code}"
            )
            body = resp.data.decode('utf-8').lower()
            assert 'courriel' in body and ('déjà' in body or 'deja' in body)

            # Still only one user with that email.
            count = User.query.filter_by(email='taken@example.qc.ca').count()
            assert count == 1
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_creates_user_with_combined_name_and_unique_username():
    """name = '<first> <last>'; username derived from email local-part, unique, max 20."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            data = dict(VALID_FORM)
            data['email'] = 'jean.tremblay@example.qc.ca'
            data['first_name'] = 'Jean'
            data['last_name'] = 'Tremblay'
            data['consent_cgu'] = 'y'
            data['consent_confidentialite'] = 'y'
            resp = client.post('/signup', data=data)
            assert resp.status_code in (200, 302)

            user = User.query.filter_by(email='jean.tremblay@example.qc.ca').first()
            assert user is not None
            assert user.name == 'Jean Tremblay'
            assert user.username
            assert len(user.username) <= 20
            # Derived from local part 'jean.tremblay' (dots stripped).
            assert user.username.startswith('jeantremblay')

            # Second signup with email producing same local-part collision.
            data2 = dict(VALID_FORM)
            data2['email'] = 'jeantremblay@otherdomain.ca'
            data2['first_name'] = 'Other'
            data2['last_name'] = 'Person'
            data2['consent_cgu'] = 'y'
            data2['consent_confidentialite'] = 'y'
            resp2 = client.post('/signup', data=data2)
            assert resp2.status_code in (200, 302)
            user2 = User.query.filter_by(email='jeantremblay@otherdomain.ca').first()
            assert user2 is not None
            assert user2.username != user.username, (
                'collision must produce a distinct unique username'
            )
            assert len(user2.username) <= 20
        finally:
            db.session.rollback()
            db.drop_all()


def test_register_redirects_to_signup_for_backward_compat():
    """GET /register returns 302 → /signup (backward-compat after redesign)."""
    with app.app_context():
        _disable_csrf()
        db.create_all()
        try:
            client = app.test_client()
            resp = client.get('/register', follow_redirects=False)
            assert resp.status_code == 302
            assert resp.headers['Location'].endswith('/signup')
        finally:
            db.session.rollback()
            db.drop_all()


def test_signup_route_csrf_enforced():
    """When WTF_CSRF_ENABLED=True, POST /signup without csrf_token is rejected."""
    with app.app_context():
        prev = app.config.get('WTF_CSRF_ENABLED', True)
        app.config['WTF_CSRF_ENABLED'] = True
        try:
            # create_all() is INSIDE the try so a failure here still restores
            # WTF_CSRF_ENABLED for the rest of the test session.
            db.create_all()
            client = app.test_client()
            data = dict(VALID_FORM)
            data['email'] = 'csrf@example.qc.ca'
            data['consent_cgu'] = 'y'
            data['consent_confidentialite'] = 'y'
            resp = client.post('/signup', data=data)
            # Flask-WTF returns 400 by default on missing/invalid CSRF token;
            # the app's error handler may also short-circuit via JSON. Either way,
            # no User must have been created.
            assert resp.status_code in (400, 403)
            assert User.query.filter_by(email='csrf@example.qc.ca').first() is None
        finally:
            app.config['WTF_CSRF_ENABLED'] = prev
            db.session.rollback()
            db.drop_all()
