"""
Tests for web/auth/routes.py

Tests cover:
- Login, register, and logout routes
- CSRF token endpoint
- Check auth endpoint
- Change password endpoint
- Integrity check endpoint
- Open redirect prevention
"""

from unittest.mock import MagicMock, patch

from flask import Flask


class TestGetCsrfToken:
    """Tests for /csrf-token endpoint."""

    def test_returns_csrf_token(self):
        """Should return CSRF token."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = True

        with patch("flask_wtf.csrf.generate_csrf") as mock_csrf:
            mock_csrf.return_value = "test_csrf_token_123"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.get("/auth/csrf-token")
                assert response.status_code == 200
                assert response.json["csrf_token"] == "test_csrf_token_123"


class TestLoginPage:
    """Tests for GET /login endpoint."""

    def test_renders_login_page(self):
        """Should render login page for unauthenticated users."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False
        app.template_folder = "templates"  # May need adjustment

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Login Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                client.get("/auth/login")
                # Should call render_template
                mock_render.assert_called()

    def test_redirects_if_already_logged_in(self):
        """Should redirect to index if user already logged in."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        @app.route("/")
        def index():
            return "Index"

        with patch(
            "local_deep_research.web.auth.routes.load_server_config"
        ) as mock_config:
            mock_config.return_value = {"allow_registrations": True}

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                with client.session_transaction() as sess:
                    sess["username"] = "testuser"

                response = client.get("/auth/login")
                assert response.status_code == 302


class TestLogin:
    """Tests for POST /login endpoint."""

    def test_returns_400_without_username(self):
        """Should return 400 when username is missing."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Login Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login",
                    data={"username": "", "password": "password123"},
                )
                assert response.status_code == 400

    def test_returns_400_without_password(self):
        """Should return 400 when password is missing."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Login Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login",
                    data={"username": "testuser", "password": ""},
                )
                assert response.status_code == 400

    def test_returns_401_for_invalid_credentials(self):
        """Should return 401 for invalid credentials."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.db_manager"
            ) as mock_db_manager,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_db_manager.open_user_database.return_value = None
            mock_render.return_value = "Login Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login",
                    data={"username": "testuser", "password": "wrongpassword"},
                )
                assert response.status_code == 401


class TestRegisterPage:
    """Tests for GET /register endpoint."""

    def test_redirects_when_registrations_disabled(self):
        """Should redirect to login when registrations are disabled."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with patch(
            "local_deep_research.web.auth.routes.load_server_config"
        ) as mock_config:
            mock_config.return_value = {"allow_registrations": False}

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.get("/auth/register")
                assert response.status_code == 302
                assert "login" in response.location


class TestRegister:
    """Tests for POST /register endpoint."""

    def test_returns_400_for_short_username(self):
        """Should return 400 when username is too short."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Register Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/register",
                    data={
                        "username": "ab",  # Too short
                        "password": "password123",
                        "confirm_password": "password123",
                        "acknowledge": "true",
                    },
                )
                assert response.status_code == 400

    def test_returns_400_for_invalid_username_chars(self):
        """Should return 400 when username contains invalid characters."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Register Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/register",
                    data={
                        "username": "test@user!",  # Invalid chars
                        "password": "password123",
                        "confirm_password": "password123",
                        "acknowledge": "true",
                    },
                )
                assert response.status_code == 400

    def test_returns_400_for_short_password(self):
        """Should return 400 when password is too short."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Register Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/register",
                    data={
                        "username": "testuser",
                        "password": "short",  # Too short
                        "confirm_password": "short",
                        "acknowledge": "true",
                    },
                )
                assert response.status_code == 400

    def test_returns_400_for_password_mismatch(self):
        """Should return 400 when passwords don't match."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Register Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/register",
                    data={
                        "username": "testuser",
                        "password": "password123",
                        "confirm_password": "different123",
                        "acknowledge": "true",
                    },
                )
                assert response.status_code == 400

    def test_returns_400_without_acknowledgment(self):
        """Should return 400 when acknowledgment not provided."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.render_template"
            ) as mock_render,
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_render.return_value = "Register Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/register",
                    data={
                        "username": "testuser",
                        "password": "password123",
                        "confirm_password": "password123",
                        # No acknowledge
                    },
                )
                assert response.status_code == 400


class TestLogout:
    """Tests for /logout endpoint."""

    def test_clears_session_on_logout(self):
        """Should clear session on logout."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch("local_deep_research.web.auth.routes.db_manager"),
            patch("local_deep_research.web.auth.routes.session_manager"),
            patch(
                "local_deep_research.database.session_passwords.session_password_store"
            ),
        ):
            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                with client.session_transaction() as sess:
                    sess["username"] = "testuser"
                    sess["session_id"] = "session_123"

                response = client.get("/auth/logout")
                assert response.status_code == 302

                with client.session_transaction() as sess:
                    assert "username" not in sess

    def test_redirects_to_login(self):
        """Should redirect to login after logout."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with (
            patch("local_deep_research.web.auth.routes.db_manager"),
            patch("local_deep_research.web.auth.routes.session_manager"),
            patch(
                "local_deep_research.database.session_passwords.session_password_store"
            ),
        ):
            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.get("/auth/logout")
                assert response.status_code == 302
                assert "login" in response.location


class TestCheckAuth:
    """Tests for /check endpoint."""

    def test_returns_authenticated_true_when_logged_in(self):
        """Should return authenticated=True when logged in."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        from local_deep_research.web.auth.routes import auth_bp

        app.register_blueprint(auth_bp)

        with app.test_client() as client:
            with client.session_transaction() as sess:
                sess["username"] = "testuser"

            response = client.get("/auth/check")
            assert response.status_code == 200
            assert response.json["authenticated"] is True
            assert response.json["username"] == "testuser"

    def test_returns_authenticated_false_when_not_logged_in(self):
        """Should return authenticated=False when not logged in."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        from local_deep_research.web.auth.routes import auth_bp

        app.register_blueprint(auth_bp)

        with app.test_client() as client:
            response = client.get("/auth/check")
            assert response.status_code == 401
            assert response.json["authenticated"] is False


class TestChangePassword:
    """Tests for /change-password endpoint."""

    def test_redirects_when_not_logged_in(self):
        """Should redirect to login when not authenticated."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        from local_deep_research.web.auth.routes import auth_bp

        app.register_blueprint(auth_bp)

        with app.test_client() as client:
            response = client.get("/auth/change-password")
            assert response.status_code == 302
            assert "login" in response.location

    def test_returns_400_without_current_password(self):
        """Should return 400 when current password is missing."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with patch(
            "local_deep_research.web.auth.routes.render_template"
        ) as mock_render:
            mock_render.return_value = "Change Password Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                with client.session_transaction() as sess:
                    sess["username"] = "testuser"

                response = client.post(
                    "/auth/change-password",
                    data={
                        "current_password": "",
                        "new_password": "newpassword123",
                        "confirm_password": "newpassword123",
                    },
                )
                assert response.status_code == 400

    def test_returns_400_when_passwords_match(self):
        """Should return 400 when new password is same as current."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with patch(
            "local_deep_research.web.auth.routes.render_template"
        ) as mock_render:
            mock_render.return_value = "Change Password Page"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                with client.session_transaction() as sess:
                    sess["username"] = "testuser"

                response = client.post(
                    "/auth/change-password",
                    data={
                        "current_password": "samepassword123",
                        "new_password": "samepassword123",
                        "confirm_password": "samepassword123",
                    },
                )
                assert response.status_code == 400


class TestIntegrityCheck:
    """Tests for /integrity-check endpoint."""

    def test_returns_401_when_not_authenticated(self):
        """Should return 401 when not authenticated."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        from local_deep_research.web.auth.routes import auth_bp

        app.register_blueprint(auth_bp)

        with app.test_client() as client:
            response = client.get("/auth/integrity-check")
            assert response.status_code == 401

    def test_returns_integrity_status(self):
        """Should return integrity status for authenticated user."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        with patch(
            "local_deep_research.web.auth.routes.db_manager"
        ) as mock_db_manager:
            mock_db_manager.check_database_integrity.return_value = True

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                with client.session_transaction() as sess:
                    sess["username"] = "testuser"

                response = client.get("/auth/integrity-check")
                assert response.status_code == 200
                assert response.json["username"] == "testuser"
                assert response.json["integrity"] == "valid"


class TestOpenRedirectPrevention:
    """Tests for open redirect prevention in login."""

    def test_blocks_external_redirect(self):
        """Should block redirect to external domain."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        @app.route("/")
        def index():
            return "Index"

        mock_engine = MagicMock()
        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.db_version_matches_package.return_value = True

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.db_manager"
            ) as mock_db_manager,
            patch(
                "local_deep_research.web.auth.routes.session_manager"
            ) as mock_session_manager,
            patch(
                "local_deep_research.web.auth.routes.get_auth_db_session"
            ) as mock_auth_db,
            patch(
                "local_deep_research.database.temp_auth.temp_auth_store"
            ) as mock_temp_auth,
            patch(
                "local_deep_research.database.session_passwords.session_password_store"
            ),
            patch(
                "local_deep_research.settings.manager.SettingsManager"
            ) as mock_settings_cls,
            patch(
                "local_deep_research.database.library_init.initialize_library_for_user"
            ),
            patch(
                "local_deep_research.news.subscription_manager.scheduler.get_news_scheduler"
            ),
            patch("local_deep_research.database.models.ProviderModel"),
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_db_manager.open_user_database.return_value = mock_engine
            mock_db_manager.get_session.return_value = mock_session
            mock_session_manager.create_session.return_value = "session_123"
            mock_auth_db.return_value = MagicMock()
            mock_settings_cls.return_value = mock_settings_manager
            mock_temp_auth.store_auth.return_value = "test_auth_token"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login?next=https://evil.com/steal",
                    data={"username": "testuser", "password": "password123"},
                )

                # Should redirect to safe URL, not evil.com
                assert response.status_code == 302
                assert "evil.com" not in response.location

    def test_allows_safe_relative_redirect(self):
        """Should allow safe relative redirects."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        @app.route("/")
        def index():
            return "Index"

        @app.route("/dashboard")
        def dashboard():
            return "Dashboard"

        mock_engine = MagicMock()
        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.db_version_matches_package.return_value = True

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.db_manager"
            ) as mock_db_manager,
            patch(
                "local_deep_research.web.auth.routes.session_manager"
            ) as mock_session_manager,
            patch(
                "local_deep_research.web.auth.routes.get_auth_db_session"
            ) as mock_auth_db,
            patch(
                "local_deep_research.database.temp_auth.temp_auth_store"
            ) as mock_temp_auth,
            patch(
                "local_deep_research.database.session_passwords.session_password_store"
            ),
            patch(
                "local_deep_research.settings.manager.SettingsManager"
            ) as mock_settings_cls,
            patch(
                "local_deep_research.database.library_init.initialize_library_for_user"
            ),
            patch(
                "local_deep_research.news.subscription_manager.scheduler.get_news_scheduler"
            ),
            patch("local_deep_research.database.models.ProviderModel"),
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_db_manager.open_user_database.return_value = mock_engine
            mock_db_manager.get_session.return_value = mock_session
            mock_session_manager.create_session.return_value = "session_123"
            mock_auth_db.return_value = MagicMock()
            mock_settings_cls.return_value = mock_settings_manager
            mock_temp_auth.store_auth.return_value = "test_auth_token"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login?next=/dashboard",
                    data={"username": "testuser", "password": "password123"},
                )

                # Should redirect to dashboard
                assert response.status_code == 302
                assert "/dashboard" in response.location

    def test_path_traversal_blocked(self):
        """Path traversal in next param is blocked by is_safe_redirect_url.

        The validator detects '..' in the URL path component and rejects
        it, so login falls back to the index page instead of redirecting
        to the traversal target.
        """
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        @app.route("/")
        def index():
            return "Index"

        mock_engine = MagicMock()
        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.db_version_matches_package.return_value = True

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.db_manager"
            ) as mock_db_manager,
            patch(
                "local_deep_research.web.auth.routes.session_manager"
            ) as mock_session_manager,
            patch(
                "local_deep_research.web.auth.routes.get_auth_db_session"
            ) as mock_auth_db,
            patch(
                "local_deep_research.database.temp_auth.temp_auth_store"
            ) as mock_temp_auth,
            patch(
                "local_deep_research.database.session_passwords.session_password_store"
            ),
            patch(
                "local_deep_research.settings.manager.SettingsManager"
            ) as mock_settings_cls,
            patch(
                "local_deep_research.database.library_init.initialize_library_for_user"
            ),
            patch(
                "local_deep_research.news.subscription_manager.scheduler.get_news_scheduler"
            ),
            patch("local_deep_research.database.models.ProviderModel"),
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_db_manager.open_user_database.return_value = mock_engine
            mock_db_manager.get_session.return_value = mock_session
            mock_session_manager.create_session.return_value = "session_123"
            mock_auth_db.return_value = MagicMock()
            mock_settings_cls.return_value = mock_settings_manager
            mock_temp_auth.store_auth.return_value = "test_auth_token"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login?next=/../../../etc/passwd",
                    data={"username": "testuser", "password": "password123"},
                )

                # Path traversal is blocked by is_safe_redirect_url,
                # so login succeeds but redirects to index (fallback)
                assert response.status_code == 302
                assert "etc/passwd" not in response.location

    def test_encoded_path_traversal_blocked(self):
        """URL-encoded path traversal (%2e%2e) in next param is blocked."""
        app = Flask(__name__)
        app.secret_key = "test"
        app.config["WTF_CSRF_ENABLED"] = False

        @app.route("/")
        def index():
            return "Index"

        mock_engine = MagicMock()
        mock_session = MagicMock()
        mock_settings_manager = MagicMock()
        mock_settings_manager.db_version_matches_package.return_value = True

        with (
            patch(
                "local_deep_research.web.auth.routes.load_server_config"
            ) as mock_config,
            patch(
                "local_deep_research.web.auth.routes.db_manager"
            ) as mock_db_manager,
            patch(
                "local_deep_research.web.auth.routes.session_manager"
            ) as mock_session_manager,
            patch(
                "local_deep_research.web.auth.routes.get_auth_db_session"
            ) as mock_auth_db,
            patch(
                "local_deep_research.database.temp_auth.temp_auth_store"
            ) as mock_temp_auth,
            patch(
                "local_deep_research.database.session_passwords.session_password_store"
            ),
            patch(
                "local_deep_research.settings.manager.SettingsManager"
            ) as mock_settings_cls,
            patch(
                "local_deep_research.database.library_init.initialize_library_for_user"
            ),
            patch(
                "local_deep_research.news.subscription_manager.scheduler.get_news_scheduler"
            ),
            patch("local_deep_research.database.models.ProviderModel"),
        ):
            mock_config.return_value = {"allow_registrations": True}
            mock_db_manager.open_user_database.return_value = mock_engine
            mock_db_manager.get_session.return_value = mock_session
            mock_session_manager.create_session.return_value = "session_123"
            mock_auth_db.return_value = MagicMock()
            mock_settings_cls.return_value = mock_settings_manager
            mock_temp_auth.store_auth.return_value = "test_auth_token"

            from local_deep_research.web.auth.routes import auth_bp

            app.register_blueprint(auth_bp)

            with app.test_client() as client:
                response = client.post(
                    "/auth/login?next=%2e%2e/admin",
                    data={"username": "testuser", "password": "password123"},
                )

                # Encoded path traversal is blocked
                assert response.status_code == 302
                assert "admin" not in response.location
