# #region test_auth [TYPE Module] [C:3] [SEMANTICS test, auth, authentication, unit]
# @BRIEF Unit tests for authentication module
# @LAYER Domain
# @RELATION BINDS_TO -> AuthPackage
from pathlib import Path
import sys

# Add src to path
sys.path.append(str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from src.core.auth.repository import AuthRepository
from src.core.auth.security import get_password_hash, verify_password
from src.core.database import Base

# Import all models to ensure they are registered with Base before create_all - must import both auth and mapping to ensure Base knows about all tables
from src.models.auth import ADGroupMapping, Permission, Role, User
from src.services.auth_service import AuthService

# Create in-memory SQLite database for testing
SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:"
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Create all tables
Base.metadata.create_all(bind=engine)
@pytest.fixture
def db_session():
    """Create a new database session with a transaction, rollback after test"""
    connection = engine.connect()
    transaction = connection.begin()
    session = TestingSessionLocal(bind=connection)
    yield session
    session.close()
    transaction.rollback()
    connection.close()
@pytest.fixture
def auth_service(db_session):
    return AuthService(db_session)
@pytest.fixture
def auth_repo(db_session):
    return AuthRepository(db_session)
# #region test_create_user [TYPE Function]
# @BRIEF Verifies that a persisted user can be retrieved with intact credential hash.
# @RELATION BINDS_TO -> [test_auth]
def test_create_user(auth_repo):
    """Test user creation"""
    user = User(
        username="testuser",
        email="test@example.com",
        password_hash=get_password_hash("testpassword123"),
        auth_source="LOCAL",
    )
    auth_repo.db.add(user)
    auth_repo.db.commit()
    retrieved_user = auth_repo.get_user_by_username("testuser")
    assert retrieved_user is not None
    assert retrieved_user.username == "testuser"
    assert retrieved_user.email == "test@example.com"
    assert verify_password("testpassword123", retrieved_user.password_hash)
# #endregion test_create_user
# #region test_authenticate_user [TYPE Function]
# @BRIEF Validates authentication outcomes for valid, wrong-password, and unknown-user cases.
# @RELATION BINDS_TO -> [test_auth]
def test_authenticate_user(auth_service, auth_repo):
    """Test user authentication with valid and invalid credentials"""
    user = User(
        username="testuser",
        email="test@example.com",
        password_hash=get_password_hash("testpassword123"),
        auth_source="LOCAL",
    )
    auth_repo.db.add(user)
    auth_repo.db.commit()
    # Test valid credentials
    authenticated_user = auth_service.authenticate_user("testuser", "testpassword123")
    assert authenticated_user is not None
    assert authenticated_user.username == "testuser"
    # Test invalid password
    invalid_user = auth_service.authenticate_user("testuser", "wrongpassword")
    assert invalid_user is None
    # Test invalid username
    invalid_user = auth_service.authenticate_user("nonexistent", "testpassword123")
    assert invalid_user is None
# #endregion test_authenticate_user
# #region test_create_session [TYPE Function]
# @BRIEF Ensures session creation returns bearer token payload fields.
# @RELATION BINDS_TO -> [test_auth]
def test_create_session(auth_service, auth_repo):
    """Test session token creation"""
    user = User(
        username="testuser",
        email="test@example.com",
        password_hash=get_password_hash("testpassword123"),
        auth_source="LOCAL",
    )
    auth_repo.db.add(user)
    auth_repo.db.commit()
    session = auth_service.create_session(user)
    assert "access_token" in session
    assert "token_type" in session
    assert session["token_type"] == "bearer"
    assert len(session["access_token"]) > 0
# #endregion test_create_session
# #region test_role_permission_association [TYPE Function]
# @BRIEF Confirms role-permission many-to-many assignments persist and reload correctly.
# @RELATION BINDS_TO -> [test_auth]
def test_role_permission_association(auth_repo):
    """Test role and permission association"""
    role = Role(name="Admin", description="System administrator")
    perm1 = Permission(resource="admin:users", action="READ")
    perm2 = Permission(resource="admin:users", action="WRITE")
    role.permissions.extend([perm1, perm2])
    auth_repo.db.add(role)
    auth_repo.db.commit()
    retrieved_role = auth_repo.get_role_by_name("Admin")
    assert retrieved_role is not None
    assert len(retrieved_role.permissions) == 2
    permissions = [f"{p.resource}:{p.action}" for p in retrieved_role.permissions]
    assert "admin:users:READ" in permissions
    assert "admin:users:WRITE" in permissions
# #endregion test_role_permission_association
# #region test_user_role_association [TYPE Function]
# @BRIEF Confirms user-role assignment persists and is queryable from repository reads.
# @RELATION BINDS_TO -> [test_auth]
def test_user_role_association(auth_repo):
    """Test user and role association"""
    role = Role(name="Admin", description="System administrator")
    user = User(
        username="adminuser",
        email="admin@example.com",
        password_hash=get_password_hash("adminpass123"),
        auth_source="LOCAL",
    )
    user.roles.append(role)
    auth_repo.db.add(role)
    auth_repo.db.add(user)
    auth_repo.db.commit()
    retrieved_user = auth_repo.get_user_by_username("adminuser")
    assert retrieved_user is not None
    assert len(retrieved_user.roles) == 1
    assert retrieved_user.roles[0].name == "Admin"
# #endregion test_user_role_association
# #region test_ad_group_mapping [TYPE Function]
# @BRIEF Verifies AD group mapping rows persist and reference the expected role.
# @RELATION BINDS_TO -> [test_auth]
def test_ad_group_mapping(auth_repo):
    """Test AD group mapping"""
    role = Role(name="ADFS_Admin", description="ADFS administrators")
    auth_repo.db.add(role)
    auth_repo.db.commit()
    mapping = ADGroupMapping(ad_group="DOMAIN\\ADFS_Admins", role_id=role.id)
    auth_repo.db.add(mapping)
    auth_repo.db.commit()
    retrieved_mapping = (
        auth_repo.db.query(ADGroupMapping)
        .filter_by(ad_group="DOMAIN\\ADFS_Admins")
        .first()
    )
    assert retrieved_mapping is not None
    assert retrieved_mapping.role_id == role.id
# #endregion test_ad_group_mapping
# #region test_authenticate_user_updates_last_login [TYPE Function]
# @BRIEF Verifies successful authentication updates last_login audit field.
# @RELATION BINDS_TO -> [test_auth]
def test_authenticate_user_updates_last_login(auth_service, auth_repo):
    """@SIDE_EFFECT: authenticate_user updates last_login timestamp on success."""
    user = User(
        username="loginuser",
        email="login@example.com",
        password_hash=get_password_hash("mypassword"),
        auth_source="LOCAL",
    )
    auth_repo.db.add(user)
    auth_repo.db.commit()
    assert user.last_login is None
    authenticated = auth_service.authenticate_user("loginuser", "mypassword")
    assert authenticated is not None
    assert authenticated.last_login is not None
# #endregion test_authenticate_user_updates_last_login
# #region test_authenticate_inactive_user [TYPE Function]
# @BRIEF Verifies inactive accounts are rejected during password authentication.
# @RELATION BINDS_TO -> [test_auth]
def test_authenticate_inactive_user(auth_service, auth_repo):
    """@PRE: User with is_active=False should not authenticate."""
    user = User(
        username="inactive_user",
        email="inactive@example.com",
        password_hash=get_password_hash("testpass"),
        auth_source="LOCAL",
        is_active=False,
    )
    auth_repo.db.add(user)
    auth_repo.db.commit()
    result = auth_service.authenticate_user("inactive_user", "testpass")
    assert result is None
# #endregion test_authenticate_inactive_user
# #region test_verify_password_empty_hash [TYPE Function]
# @BRIEF Verifies password verification safely rejects empty or null password hashes.
# @RELATION BINDS_TO -> [test_auth]
def test_verify_password_empty_hash():
    """@PRE: verify_password with empty/None hash returns False."""
    assert verify_password("anypassword", "") is False
    assert verify_password("anypassword", None) is False
# #endregion test_verify_password_empty_hash
# #region test_provision_adfs_user_new [TYPE Function]
# @BRIEF Verifies JIT provisioning creates a new ADFS user and maps group-derived roles.
# @RELATION BINDS_TO -> [test_auth]
def test_provision_adfs_user_new(auth_service, auth_repo):
    """@POST: provision_adfs_user creates a new ADFS user with correct roles."""
    # Set up a role and AD group mapping
    role = Role(name="ADFS_Viewer", description="ADFS viewer role")
    auth_repo.db.add(role)
    auth_repo.db.commit()
    mapping = ADGroupMapping(ad_group="DOMAIN\\Viewers", role_id=role.id)
    auth_repo.db.add(mapping)
    auth_repo.db.commit()
    user_info = {
        "upn": "newadfsuser@domain.com",
        "email": "newadfsuser@domain.com",
        "groups": ["DOMAIN\\Viewers"],
    }
    user = auth_service.provision_adfs_user(user_info)
    assert user is not None
    assert user.username == "newadfsuser@domain.com"
    assert user.auth_source == "ADFS"
    assert user.is_active is True
    assert len(user.roles) == 1
    assert user.roles[0].name == "ADFS_Viewer"
# #endregion test_provision_adfs_user_new
# #region test_provision_adfs_user_existing [TYPE Function]
# @BRIEF Verifies JIT provisioning reuses existing ADFS user and refreshes role assignments.
# @RELATION BINDS_TO -> [test_auth]
def test_provision_adfs_user_existing(auth_service, auth_repo):
    """@POST: provision_adfs_user updates roles for existing user."""
    # Create existing user
    existing = User(
        username="existingadfs@domain.com",
        email="existingadfs@domain.com",
        auth_source="ADFS",
        is_active=True,
    )
    auth_repo.db.add(existing)
    auth_repo.db.commit()
    user_info = {
        "upn": "existingadfs@domain.com",
        "email": "existingadfs@domain.com",
        "groups": [],
    }
    user = auth_service.provision_adfs_user(user_info)
    assert user is not None
    assert user.username == "existingadfs@domain.com"
    assert len(user.roles) == 0  # No matching group mappings
# #endregion test_provision_adfs_user_existing
# #endregion test_auth
