# #region Test.Api.Auth [C:3] [TYPE Module] [SEMANTICS test,auth,api]
# @BRIEF Unit tests for auth API routes — login, logout, me, ADFS.
# @RELATION BINDS_TO -> [Api.Auth]
# @TEST_EDGE: invalid_credentials -> 401
# @TEST_EDGE: locked_account -> 429 (rate limited)
# @TEST_EDGE: missing_fields -> 422
# @TEST_EDGE: already_expired_token -> 200 (idempotent logout)

import os

# Set env BEFORE any source imports
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")

import sys
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from fastapi import FastAPI, HTTPException, status
from fastapi.testclient import TestClient

_src = str(Path(__file__).resolve().parent.parent.parent / "src")
if _src not in sys.path:
    sys.path.insert(0, _src)


@pytest.fixture(autouse=True)
def _patch_rate_limiter():
    """Ensure rate limiter is safe."""
    with patch("src.api.auth.rate_limiter") as mock_rl:
        mock_rl.is_banned.return_value = False
        mock_rl.record_attempt = MagicMock()
        mock_rl.record_success = MagicMock()
        yield


# ── Helper: build TestClient with auth dependencies overridden ──

def _make_client() -> TestClient:
    """Build a TestClient for the auth router with all dependencies overridden."""
    from src.api.auth import router
    from src.core.database import get_auth_db
    from src.dependencies import get_current_user
    from src.schemas.auth import User, RoleSchema

    app = FastAPI()
    app.include_router(router)

    mock_db = MagicMock()
    mock_user = User(
        id="user-1",
        username="testuser",
        email="test@example.com",
        auth_source="LOCAL",
        created_at=__import__("datetime").datetime.now(),
        roles=[RoleSchema(id="role-1", name="Admin", description="Admin role", permissions=[])],
    )

    app.dependency_overrides[get_auth_db] = lambda: mock_db
    app.dependency_overrides[get_current_user] = lambda: mock_user
    return TestClient(app)


# ── Tests ──

class TestLogin:
    """POST /api/auth/login"""

    def test_login_success(self):
        """Happy path: valid credentials return 200 + Token."""
        mock_user = MagicMock()
        mock_user.username = "testuser"
        mock_token = MagicMock()
        mock_token.access_token = "abc.def.ghi"
        mock_token.token_type = "bearer"

        with patch("src.api.auth.AuthService") as MockAuth:
            auth_instance = MockAuth.return_value
            auth_instance.authenticate_user.return_value = mock_user
            auth_instance.create_session.return_value = mock_token

            client = _make_client()
            resp = client.post("/api/auth/login", data={"username": "testuser", "password": "secret123"})

        assert resp.status_code == 200
        assert resp.json()["access_token"] == "abc.def.ghi"
        assert resp.json()["token_type"] == "bearer"

    def test_login_invalid_credentials(self):
        """Invalid credentials return 401."""
        with patch("src.api.auth.AuthService") as MockAuth:
            auth_instance = MockAuth.return_value
            auth_instance.authenticate_user.return_value = None

            client = _make_client()
            resp = client.post("/api/auth/login", data={"username": "bad", "password": "wrong"})

        assert resp.status_code == 401
        assert "Incorrect username or password" in resp.text

    def test_login_rate_limited(self):
        """Banned IP returns 429."""
        with patch("src.api.auth.rate_limiter") as mock_rl:
            mock_rl.is_banned.return_value = True
            client = _make_client()
            resp = client.post("/api/auth/login", data={"username": "test", "password": "test"})

        assert resp.status_code == 429
        assert "Too many login attempts" in resp.text

    def test_login_missing_fields(self):
        """Missing form data returns 422."""
        client = _make_client()
        resp = client.post("/api/auth/login", data={})
        assert resp.status_code == 422


class TestMe:
    """GET /api/auth/me"""

    def test_me_authenticated(self):
        """Authenticated user returns profile."""
        client = _make_client()
        resp = client.get("/api/auth/me")
        assert resp.status_code == 200
        data = resp.json()
        assert data["username"] == "testuser"
        assert data["email"] == "test@example.com"

    def test_me_unauthenticated(self):
        """Unauthenticated request returns 401."""
        from src.core.database import get_auth_db
        from src.dependencies import get_current_user

        app = FastAPI()
        from src.api.auth import router
        app.include_router(router)
        app.dependency_overrides[get_current_user] = lambda: (_ for _ in ()).throw(
            HTTPException(status_code=401, detail="Not authenticated")
        )
        app.dependency_overrides[get_auth_db] = lambda: MagicMock()
        client = TestClient(app)
        resp = client.get("/api/auth/me")
        assert resp.status_code == 401


class TestLogout:
    """POST /api/auth/logout"""

    def test_logout_success(self):
        """Valid token blacklisted, returns 200."""
        with patch("src.api.auth.blacklist_token") as mock_blacklist:
            client = _make_client()
            resp = client.post(
                "/api/auth/logout",
                headers={"Authorization": "Bearer some.jwt.token"},
            )
        assert resp.status_code == 200
        assert resp.json()["message"] == "Successfully logged out"
        mock_blacklist.assert_called_once()

    def test_logout_no_token_header(self):
        """No Authorization header still succeeds (no token to blacklist)."""
        with patch("src.api.auth.blacklist_token") as mock_blacklist:
            client = _make_client()
            resp = client.post("/api/auth/logout")
        assert resp.status_code == 200
        mock_blacklist.assert_not_called()

    def test_logout_expired_token(self):
        """Expired token — still returns 200 (idempotent)."""
        with patch("src.api.auth.blacklist_token") as mock_blacklist:
            client = _make_client()
            resp = client.post(
                "/api/auth/logout",
                headers={"Authorization": "Bearer expired.token.here"},
            )
        assert resp.status_code == 200
        mock_blacklist.assert_called_once()


class TestLoginAdfs:
    """GET /api/auth/login/adfs"""

    def test_adfs_not_configured(self):
        """ADFS not configured returns 503."""
        with patch("src.api.auth.is_adfs_configured", return_value=False):
            client = _make_client()
            resp = client.get("/api/auth/login/adfs")
        assert resp.status_code == 503
        assert "ADFS is not configured" in resp.text

    def test_adfs_redirect(self):
        """ADFS configured redirects to provider."""
        mock_oauth = MagicMock()
        mock_oauth.adfs.authorize_redirect = AsyncMock(return_value=None)

        with patch("src.api.auth.is_adfs_configured", return_value=True), \
             patch("src.api.auth.oauth", mock_oauth):
            client = _make_client()
            resp = client.get("/api/auth/login/adfs")
        assert resp.status_code in (200, 307)


class TestCallbackAdfs:
    """GET /api/auth/callback/adfs"""

    def test_callback_not_configured(self):
        """ADFS not configured returns 503."""
        with patch("src.api.auth.is_adfs_configured", return_value=False):
            client = _make_client()
            resp = client.get("/api/auth/callback/adfs")
        assert resp.status_code == 503

    def test_callback_no_userinfo(self):
        """ADFS token without userinfo returns 400."""
        mock_oauth = MagicMock()
        mock_oauth.adfs.authorize_access_token = AsyncMock(return_value={})

        with patch("src.api.auth.is_adfs_configured", return_value=True), \
             patch("src.api.auth.oauth", mock_oauth):
            client = _make_client()
            resp = client.get("/api/auth/callback/adfs")
        assert resp.status_code == 400
        assert "Failed to retrieve user info" in resp.text

    def test_callback_success(self):
        """ADFS callback provisions user and returns token."""
        from types import SimpleNamespace

        mock_token = SimpleNamespace(
            access_token="adfs.token.xyz",
            token_type="bearer",
        )
        mock_user_info = {"sub": "adfs-user", "email": "adfs@example.com"}

        mock_oauth = MagicMock()
        mock_oauth.adfs.authorize_access_token = AsyncMock(
            return_value={"userinfo": mock_user_info}
        )

        with patch("src.api.auth.is_adfs_configured", return_value=True), \
             patch("src.api.auth.oauth", mock_oauth), \
             patch("src.api.auth.AuthService") as MockAuth:
            auth_instance = MockAuth.return_value
            auth_instance.provision_adfs_user.return_value = MagicMock()
            auth_instance.create_session.return_value = mock_token

            client = _make_client()
            resp = client.get("/api/auth/callback/adfs")
        assert resp.status_code == 200
        assert resp.json()["access_token"] == "adfs.token.xyz"
# #endregion Test.Api.Auth
