"""
Tests de Seguridad — Core
===========================
Verifica: bloqueo de cuenta, Argon2 hasher, headers de seguridad.
"""

from django.conf import settings
from django.contrib.auth import authenticate, get_user_model
from django.test import RequestFactory, TestCase, override_settings

from apps.core.models import LoginAttempt

User = get_user_model()


class Argon2HasherTest(TestCase):
    """Verifica que Argon2 es el hasher principal."""

    def test_argon2_is_primary_hasher(self):
        self.assertEqual(
            settings.PASSWORD_HASHERS[0],
            "django.contrib.auth.hashers.Argon2PasswordHasher",
        )

    def test_password_is_hashed_with_argon2(self):
        user = User.objects.create_user(
            email="testargon@test.com",
            password="SuperSecure!123",
            nombre="Test",
            apellido="Argon",
        )
        self.assertTrue(user.password.startswith("argon2"))


class AccountLockoutTest(TestCase):
    """Verifica el bloqueo de cuenta tras intentos fallidos."""

    def setUp(self):
        self.factory = RequestFactory()
        self.user = User.objects.create_user(
            email="locktest@test.com",
            password="CorrectPassword!123",
            nombre="Lock",
            apellido="Test",
        )

    def _make_request(self):
        request = self.factory.post("/fake-login/")
        request.META["REMOTE_ADDR"] = "192.168.1.100"
        request.META["HTTP_USER_AGENT"] = "TestAgent"
        return request

    @override_settings(ACCOUNT_LOGIN_MAX_ATTEMPTS=5, ACCOUNT_LOGIN_LOCKOUT_MINUTES=30)
    def test_successful_login(self):
        request = self._make_request()
        user = authenticate(request, username="locktest@test.com", password="CorrectPassword!123")
        self.assertIsNotNone(user)
        self.assertEqual(user.email, "locktest@test.com")

    @override_settings(ACCOUNT_LOGIN_MAX_ATTEMPTS=5, ACCOUNT_LOGIN_LOCKOUT_MINUTES=30)
    def test_failed_login_records_attempt(self):
        request = self._make_request()
        user = authenticate(request, username="locktest@test.com", password="WrongPassword")
        self.assertIsNone(user)
        self.assertEqual(LoginAttempt.objects.filter(username="locktest@test.com", was_successful=False).count(), 1)

    @override_settings(ACCOUNT_LOGIN_MAX_ATTEMPTS=5, ACCOUNT_LOGIN_LOCKOUT_MINUTES=30)
    def test_account_locks_after_max_attempts(self):
        """5 intentos fallidos bloquean la cuenta."""
        for i in range(5):
            request = self._make_request()
            authenticate(request, username="locktest@test.com", password="WrongPassword")

        # Verificar que está bloqueada
        self.assertTrue(LoginAttempt.is_locked("locktest@test.com"))

        # Incluso con contraseña correcta, no puede entrar
        request = self._make_request()
        user = authenticate(request, username="locktest@test.com", password="CorrectPassword!123")
        self.assertIsNone(user)

    @override_settings(ACCOUNT_LOGIN_MAX_ATTEMPTS=5, ACCOUNT_LOGIN_LOCKOUT_MINUTES=30)
    def test_successful_login_clears_failures(self):
        """Un login exitoso limpia los intentos fallidos."""
        # 3 intentos fallidos
        for i in range(3):
            request = self._make_request()
            authenticate(request, username="locktest@test.com", password="WrongPassword")

        self.assertEqual(LoginAttempt.get_recent_failures("locktest@test.com"), 3)

        # Login exitoso
        request = self._make_request()
        authenticate(request, username="locktest@test.com", password="CorrectPassword!123")

        # Intentos fallidos limpiados
        self.assertEqual(LoginAttempt.get_recent_failures("locktest@test.com"), 0)

    @override_settings(ACCOUNT_LOGIN_MAX_ATTEMPTS=5, ACCOUNT_LOGIN_LOCKOUT_MINUTES=30)
    def test_lockout_does_not_affect_other_users(self):
        """El bloqueo es por usuario, no global."""
        other_user = User.objects.create_user(
            email="other@test.com",
            password="OtherPassword!123",
            nombre="Other",
            apellido="User",
        )

        # Bloquear locktest
        for i in range(5):
            request = self._make_request()
            authenticate(request, username="locktest@test.com", password="WrongPassword")

        self.assertTrue(LoginAttempt.is_locked("locktest@test.com"))
        self.assertFalse(LoginAttempt.is_locked("other@test.com"))

        # El otro usuario sí puede entrar
        request = self._make_request()
        user = authenticate(request, username="other@test.com", password="OtherPassword!123")
        self.assertIsNotNone(user)


class SecurityHeadersTest(TestCase):
    """Verifica headers de seguridad en las respuestas."""

    def test_x_frame_options(self):
        self.assertEqual(settings.X_FRAME_OPTIONS, "DENY")

    def test_content_type_nosniff(self):
        self.assertTrue(settings.SECURE_CONTENT_TYPE_NOSNIFF)

    def test_referrer_policy(self):
        self.assertEqual(settings.SECURE_REFERRER_POLICY, "strict-origin-when-cross-origin")

    def test_session_cookie_httponly(self):
        self.assertTrue(settings.SESSION_COOKIE_HTTPONLY)

    def test_csrf_cookie_httponly(self):
        self.assertTrue(settings.CSRF_COOKIE_HTTPONLY)

    def test_cors_no_wildcard(self):
        self.assertFalse(settings.CORS_ALLOW_ALL_ORIGINS)

    def test_csp_configured(self):
        csp = settings.CONTENT_SECURITY_POLICY
        self.assertIn("DIRECTIVES", csp)
        self.assertIn("default-src", csp["DIRECTIVES"])
        self.assertEqual(csp["DIRECTIVES"]["default-src"], ["'self'"])
        self.assertEqual(csp["DIRECTIVES"]["frame-ancestors"], ["'none'"])

    def test_password_validators_configured(self):
        self.assertTrue(len(settings.AUTH_PASSWORD_VALIDATORS) >= 4)

    def test_session_cookie_age(self):
        self.assertEqual(settings.SESSION_COOKIE_AGE, 3600)


class SecuritySettingsTest(TestCase):
    """Verifica configuraciones de seguridad críticas."""

    def test_authentication_backend_is_secure(self):
        self.assertIn(
            "apps.core.backends.SecureModelBackend",
            settings.AUTHENTICATION_BACKENDS,
        )

    def test_rate_limiting_enabled(self):
        self.assertTrue(settings.RATELIMIT_ENABLE)

    def test_rate_limiting_fail_closed(self):
        self.assertFalse(settings.RATELIMIT_FAIL_OPEN)
