# #region Test.AppModule.WsAuth [C:3] [TYPE Module] [SEMANTICS test,app,ws,auth,jwt,apikey]
# @BRIEF Tests for app.py — _authenticate_websocket: JWT/API key auth for WebSocket connections.
# @RELATION BINDS_TO -> [AppModule]
# @TEST_EDGE: ws_missing_token -> returns False
# @TEST_EDGE: ws_jwt_auth -> accepts valid JWT
# @TEST_EDGE: ws_jwt_fallback_apikey -> accepts valid API key after JWT fail
# @TEST_EDGE: ws_both_fail -> returns False when both auth methods fail
# @TEST_INVARIANT: ws_auth_gate -> VERIFIED_BY: test_websocket_auth_missing_token, test_websocket_auth_jwt_valid, test_websocket_auth_api_key

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

import pytest
from unittest.mock import AsyncMock, MagicMock, patch


class TestAuthenticateWebsocket:
    """_authenticate_websocket — JWT/API key auth."""

    # #region test_missing_token [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_missing_token(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {}
        assert await _authenticate_websocket(ws, "ws/logs") is False
    # #endregion test_missing_token

    # #region test_jwt_valid [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_jwt_valid(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {"token": "valid.jwt"}
        with patch("src.core.auth.jwt.decode_token", return_value={"sub": "testuser"}) as md:
            assert await _authenticate_websocket(ws, "ws/logs") is True
            md.assert_called_once_with("valid.jwt")
    # #endregion test_jwt_valid

    # #region test_jwt_no_sub [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_jwt_no_sub(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {"token": "no-sub"}
        with (
            patch("src.core.auth.jwt.decode_token", return_value={"role": "admin"}),
            patch("src.core.auth.api_key.hash_api_key", return_value="hk"),
            patch("src.core.database.SessionLocal") as msl,
        ):
            msl.return_value = MagicMock()
            msl.return_value.query.return_value.filter.return_value.first.return_value = None
            assert await _authenticate_websocket(ws, "ws/logs") is False
    # #endregion test_jwt_no_sub

    # #region test_jwt_fallback_apikey [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_jwt_fallback_apikey(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {"token": "valid-key"}
        key = MagicMock(); key.active = True; key.name = "Test"
        with (
            patch("src.core.auth.jwt.decode_token", side_effect=Exception("expired")),
            patch("src.core.auth.api_key.hash_api_key", return_value="hk"),
            patch("src.core.database.SessionLocal") as msl,
        ):
            msl.return_value = MagicMock()
            msl.return_value.query.return_value.filter.return_value.first.return_value = key
            assert await _authenticate_websocket(ws, "ws/logs") is True
    # #endregion test_jwt_fallback_apikey

    # #region test_apikey_inactive [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_apikey_inactive(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {"token": "inactive"}
        key = MagicMock(); key.active = False
        with (
            patch("src.core.auth.jwt.decode_token", side_effect=Exception("fail")),
            patch("src.core.auth.api_key.hash_api_key", return_value="hk"),
            patch("src.core.database.SessionLocal") as msl,
        ):
            msl.return_value = MagicMock()
            msl.return_value.query.return_value.filter.return_value.first.return_value = key
            assert await _authenticate_websocket(ws, "ws/logs") is False
    # #endregion test_apikey_inactive

    # #region test_both_fail [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_both_fail(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {"token": "bad"}
        with (
            patch("src.core.auth.jwt.decode_token", side_effect=Exception("invalid")),
            patch("src.core.auth.api_key.hash_api_key", return_value="hk"),
            patch("src.core.database.SessionLocal") as msl,
        ):
            msl.return_value = MagicMock()
            msl.return_value.query.return_value.filter.return_value.first.return_value = None
            assert await _authenticate_websocket(ws, "ws/logs") is False
    # #endregion test_both_fail


class TestAuthenticateWebsocketApiKeyException:
    """_authenticate_websocket — API key DB exception is caught."""

    # #region test_apikey_db_exception [C:2] [TYPE Function]
    @pytest.mark.asyncio
    async def test_apikey_db_exception(self):
        from src.app import _authenticate_websocket
        ws = MagicMock(); ws.query_params = {"token": "crash"}
        with (
            patch("src.core.auth.jwt.decode_token", side_effect=Exception("JWT fail")),
            patch("src.core.auth.api_key.hash_api_key", return_value="hk"),
            patch("src.core.database.SessionLocal") as msl,
        ):
            db = MagicMock(); db.query.side_effect = Exception("DB lost")
            msl.return_value = db
            assert await _authenticate_websocket(ws, "ws/logs") is False
    # #endregion test_apikey_db_exception
# #endregion Test.AppModule.WsAuth
