"""
Comprehensive tests for Irene Auth - All 8 Features

Tests all features:
1. User Model
2. Password Hashing (Argon2id)
3. Signup + Login
4. JWT Access Tokens
5. Refresh Tokens
6. Role-Based Access Control
7. Password Reset
8. Email Verification
"""

import pytest
import os
from datetime import datetime, timedelta
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

# Test imports for Rust crypto
import irene_auth

# Test imports for Python auth
from irene_auth import (
    AuthManager,
    AuthConfig,
    User,
    Role,
    RefreshToken,
    RBACManager,
    Base
)


# ============================================================================
# Test Configuration
# ============================================================================

@pytest.fixture(scope="function")
def test_config():
    """Create test configuration"""
    return AuthConfig(
        database_url="sqlite:///:memory:",  # In-memory database for tests
        secret_key="test-secret-key-min-32-characters-long-for-testing",
        algorithm="HS256",
        access_token_expire_minutes=15,
        refresh_token_expire_days=30,
        verification_token_expire_hours=24,
        reset_token_expire_hours=1,
    )


@pytest.fixture(scope="function")
def auth_manager(test_config):
    """Create AuthManager with fresh database"""
    manager = AuthManager(test_config)
    yield manager
    # Cleanup
    manager.engine.dispose()


@pytest.fixture(scope="function")
def db_session(auth_manager):
    """Get database session"""
    session = auth_manager.get_session()
    yield session
    session.close()


# ============================================================================
# Feature 2: Password Hashing Tests (Rust Argon2id)
# ============================================================================

def test_password_hashing_rust():
    """Test Rust password hashing with Argon2id"""
    print("\n🔐 Testing Feature 2: Password Hashing (Argon2id)")
    
    password = "SecurePassword123!"
    
    # Hash password
    hash1 = irene_auth.hash_password(password)
    print(f"  ✓ Password hashed: {hash1[:50]}...")
    
    # Verify correct password
    result = irene_auth.verify_password(password, hash1)
    assert result == True, "Password verification should succeed"
    print(f"  ✓ Correct password verified")
    
    # Verify wrong password
    try:
        irene_auth.verify_password("WrongPassword", hash1)
        assert False, "Should raise exception for wrong password"
    except Exception:
        print(f"  ✓ Wrong password rejected")
    
    # Different hashes for same password (salted)
    hash2 = irene_auth.hash_password(password)
    assert hash1 != hash2, "Same password should produce different hashes (salted)"
    print(f"  ✓ Salted hashing confirmed")
    
    print("  ✅ Feature 2: PASS\n")


# ============================================================================
# Token Generation & Hashing Tests (Rust CSPRNG + SHA-256)
# ============================================================================

def test_token_generation_rust():
    """Test Rust token generation and hashing"""
    print("\n🎲 Testing Token Generation (CSPRNG)")
    
    # Generate tokens
    token1 = irene_auth.generate_token(32)
    token2 = irene_auth.generate_token(32)
    print(f"  ✓ Token 1: {token1[:30]}...")
    print(f"  ✓ Token 2: {token2[:30]}...")
    
    assert len(token1) > 0, "Token should not be empty"
    assert token1 != token2, "Tokens should be unique (CSPRNG)"
    print(f"  ✓ Unique tokens generated")
    
    # Hash token
    token_hash = irene_auth.hash_token(token1)
    print(f"  ✓ Token hash: {token_hash}")
    assert len(token_hash) == 64, "SHA-256 hash should be 64 hex chars"
    
    # Verify token (constant-time comparison)
    result = irene_auth.verify_token_hash(token1, token_hash)
    assert result == True, "Token verification should succeed"
    print(f"  ✓ Token verified with constant-time comparison")
    
    # Wrong token
    result_wrong = irene_auth.verify_token_hash(token2, token_hash)
    assert result_wrong == False, "Wrong token should fail verification"
    print(f"  ✓ Wrong token rejected")
    
    print("  ✅ Token Operations: PASS\n")


# ============================================================================
# Feature 4: JWT Access Tokens Tests (Rust HS256)
# ============================================================================

def test_jwt_operations_rust():
    """Test Rust JWT encoding and decoding"""
    print("\n🔑 Testing Feature 4: JWT Access Tokens")
    
    secret = b"test-secret-key-min-32-characters"
    
    # Encode JWT
    payload = {
        "email": "user@example.com",
        "role": "admin",
        "custom_field": "test_value"
    }
    jwt_token = irene_auth.jwt_encode(payload, secret, 3600, "HS256")
    print(f"  ✓ JWT encoded: {jwt_token[:50]}...")
    
    # Decode JWT
    decoded = irene_auth.jwt_decode(jwt_token, secret, ["HS256"])
    print(f"  ✓ JWT decoded: {decoded}")
    
    assert "sub" in decoded or "email" in decoded, "Decoded JWT should contain claims"
    assert "exp" in decoded, "JWT should have expiration"
    print(f"  ✓ JWT claims verified")
    
    # Wrong secret
    try:
        irene_auth.jwt_decode(jwt_token, b"wrong-secret", ["HS256"])
        assert False, "Should fail with wrong secret"
    except Exception:
        print(f"  ✓ Wrong secret rejected")
    
    print("  ✅ Feature 4: PASS\n")


# ============================================================================
# Feature 1: User Model Tests
# ============================================================================

def test_user_model(db_session):
    """Test Feature 1: User Model"""
    print("\n👤 Testing Feature 1: User Model")
    
    # Create user
    user = User(
        email="test@example.com",
        password_hash=irene_auth.hash_password("password123"),
        is_active=True,
        is_verified=False
    )
    db_session.add(user)
    db_session.commit()
    print(f"  ✓ User created: {user}")
    
    # Query user
    found = db_session.query(User).filter_by(email="test@example.com").first()
    assert found is not None, "User should be found"
    assert found.email == "test@example.com"
    print(f"  ✓ User queried: {found}")
    
    # User attributes
    assert hasattr(found, 'roles'), "User should have roles relationship"
    assert hasattr(found, 'refresh_tokens'), "User should have refresh_tokens relationship"
    print(f"  ✓ User relationships exist")
    
    print("  ✅ Feature 1: PASS\n")


# ============================================================================
# Feature 3: Signup + Login Tests
# ============================================================================

def test_signup_login(auth_manager, db_session):
    """Test Feature 3: Signup + Login"""
    print("\n📝 Testing Feature 3: Signup + Login")
    
    # Test signup
    email = "newuser@example.com"
    password = "SecurePass123!"
    
    user, verification_token = auth_manager.signup(
        email=email,
        password=password,
        require_verification=True,
        db=db_session
    )
    print(f"  ✓ Signup successful: {user.email}")
    print(f"  ✓ Verification token: {verification_token[:30]}...")
    
    assert user.email == email
    assert user.is_verified == False, "User should not be verified initially"
    assert verification_token is not None
    
    # Test duplicate email
    try:
        auth_manager.signup(email, "another_pass", db=db_session)
        assert False, "Should raise error for duplicate email"
    except Exception as e:
        print(f"  ✓ Duplicate email rejected: {type(e).__name__}")
    
    # Verify email first (for login test)
    success = auth_manager.verification_manager.verify_email(verification_token, db_session)
    assert success == True
    db_session.commit()
    print(f"  ✓ Email verified")
    
    # Test login
    tokens = auth_manager.login(
        email=email,
        password=password,
        require_verified=True,
        db=db_session
    )
    print(f"  ✓ Login successful")
    print(f"  ✓ Access token: {tokens['access_token'][:50]}...")
    print(f"  ✓ Refresh token: {tokens['refresh_token'][:30]}...")
    
    assert "access_token" in tokens
    assert "refresh_token" in tokens
    assert tokens["token_type"] == "bearer"
    
    # Test wrong password
    try:
        auth_manager.login(email, "WrongPassword", db=db_session)
        assert False, "Should raise error for wrong password"
    except Exception as e:
        print(f"  ✓ Wrong password rejected: {type(e).__name__}")
    
    print("  ✅ Feature 3: PASS\n")


# ============================================================================
# Feature 5: Refresh Tokens Tests
# ============================================================================

def test_refresh_tokens(auth_manager, db_session):
    """Test Feature 5: Refresh Tokens"""
    print("\n🔄 Testing Feature 5: Refresh Tokens")
    
    # Create user
    user, _ = auth_manager.signup(
        "refresh@example.com",
        "password123",
        require_verification=False,
        db=db_session
    )
    
    # Create refresh token
    refresh_token = auth_manager.refresh_manager.create_refresh_token(user, db_session)
    print(f"  ✓ Refresh token created: {refresh_token[:30]}...")
    
    # Verify token is stored
    token_record = db_session.query(RefreshToken).filter_by(user_id=user.id).first()
    assert token_record is not None
    assert token_record.revoked == False
    print(f"  ✓ Token stored in database")
    
    # Use refresh token to get new access token
    new_access_token = auth_manager.refresh_access_token(refresh_token, db_session)
    print(f"  ✓ New access token: {new_access_token[:50]}...")
    
    # Revoke token
    revoked = auth_manager.refresh_manager.revoke_token(refresh_token, db_session)
    assert revoked == True
    print(f"  ✓ Token revoked")
    
    # Try to use revoked token
    try:
        auth_manager.refresh_access_token(refresh_token, db_session)
        assert False, "Should fail with revoked token"
    except Exception as e:
        print(f"  ✓ Revoked token rejected: {type(e).__name__}")
    
    print("  ✅ Feature 5: PASS\n")


# ============================================================================
# Feature 6: RBAC Tests
# ============================================================================

def test_rbac(auth_manager, db_session):
    """Test Feature 6: Role-Based Access Control"""
    print("\n🛡️ Testing Feature 6: RBAC")
    
    rbac = RBACManager()
    
    # Create roles
    admin_role = rbac.create_role("admin", "Administrator", db_session)
    user_role = rbac.create_role("user", "Regular User", db_session)
    mod_role = rbac.create_role("moderator", "Moderator", db_session)
    print(f"  ✓ Roles created: admin, user, moderator")
    
    # Create user
    user, _ = auth_manager.signup(
        "rbac@example.com",
        "password123",
        require_verification=False,
        db=db_session
    )
    
    # Assign roles
    rbac.assign_role(user, "user", db_session)
    rbac.assign_role(user, "moderator", db_session)
    db_session.refresh(user)
    print(f"  ✓ Roles assigned to user")
    
    # Check roles
    assert user.has_role("user") == True
    assert user.has_role("moderator") == True
    assert user.has_role("admin") == False
    print(f"  ✓ Role checks working")
    
    # Check any role
    assert user.has_any_role(["admin", "moderator"]) == True
    assert user.has_any_role(["admin", "superuser"]) == False
    print(f"  ✓ Any role checks working")
    
    # Remove role
    rbac.remove_role(user, "moderator", db_session)
    db_session.refresh(user)
    assert user.has_role("moderator") == False
    print(f"  ✓ Role removal working")
    
    print("  ✅ Feature 6: PASS\n")


# ============================================================================
# Feature 7: Password Reset Tests
# ============================================================================

def test_password_reset(auth_manager, db_session):
    """Test Feature 7: Password Reset"""
    print("\n🔑 Testing Feature 7: Password Reset")
    
    # Create user
    email = "reset@example.com"
    old_password = "OldPassword123!"
    user, _ = auth_manager.signup(email, old_password, require_verification=False, db=db_session)
    
    # Request password reset
    reset_token = auth_manager.reset_manager.request_password_reset(email, db_session)
    print(f"  ✓ Reset token generated: {reset_token[:30]}...")
    
    db_session.refresh(user)
    assert user.reset_token_hash is not None
    assert user.reset_token_expires is not None
    print(f"  ✓ Reset token stored in database")
    
    # Verify token
    verified_user = auth_manager.reset_manager.verify_reset_token(reset_token, db_session)
    assert verified_user is not None
    assert verified_user.id == user.id
    print(f"  ✓ Reset token verified")
    
    # Reset password
    new_password = "NewPassword456!"
    success = auth_manager.reset_manager.reset_password(reset_token, new_password, db_session)
    assert success == True
    print(f"  ✓ Password reset successful")
    
    db_session.refresh(user)
    assert user.reset_token_hash is None, "Reset token should be cleared"
    
    # Try to login with new password
    tokens = auth_manager.login(email, new_password, require_verified=False, db=db_session)
    assert tokens is not None
    print(f"  ✓ Login with new password successful")
    
    # Try to login with old password (should fail)
    try:
        auth_manager.login(email, old_password, require_verified=False, db=db_session)
        assert False, "Should fail with old password"
    except Exception:
        print(f"  ✓ Old password rejected")
    
    print("  ✅ Feature 7: PASS\n")


# ============================================================================
# Feature 8: Email Verification Tests
# ============================================================================

def test_email_verification(auth_manager, db_session):
    """Test Feature 8: Email Verification"""
    print("\n📧 Testing Feature 8: Email Verification")
    
    # Create user with verification
    email = "verify@example.com"
    user, verification_token = auth_manager.signup(
        email,
        "password123",
        require_verification=True,
        db=db_session
    )
    print(f"  ✓ User created with verification token")
    print(f"  ✓ Verification token: {verification_token[:30]}...")
    
    db_session.refresh(user)
    assert user.is_verified == False
    assert user.verification_token_hash is not None
    
    # Verify email
    success = auth_manager.verification_manager.verify_email(verification_token, db_session)
    assert success == True
    print(f"  ✓ Email verified successfully")
    
    db_session.refresh(user)
    assert user.is_verified == True
    assert user.verification_token_hash is None, "Token should be cleared"
    
    # Try to verify again (should fail)
    success = auth_manager.verification_manager.verify_email(verification_token, db_session)
    assert success == False
    print(f"  ✓ Already verified - rejected")
    
    # Test resend verification for unverified user
    email2 = "verify2@example.com"
    user2, token1 = auth_manager.signup(email2, "pass123", require_verification=True, db=db_session)
    
    # Resend
    token2 = auth_manager.verification_manager.resend_verification(email2, db_session)
    assert token2 is not None
    assert token2 != token1, "New token should be different"
    print(f"  ✓ Verification resent with new token")
    
    print("  ✅ Feature 8: PASS\n")


# ============================================================================
# Integration Test: Complete Workflow
# ============================================================================

def test_complete_workflow(auth_manager, db_session):
    """Integration test: Complete authentication workflow"""
    print("\n🔄 Testing Complete Workflow (All Features)")
    
    rbac = RBACManager()
    
    # Create roles (Feature 6)
    rbac.create_role("admin", "Administrator", db_session)
    rbac.create_role("user", "Regular User", db_session)
    print(f"  1. ✓ Roles created (Feature 6)")
    
    # Signup (Feature 3 + 2)
    email = "integration@example.com"
    password = "IntegrationTest123!"
    user, verification_token = auth_manager.signup(email, password, db=db_session)
    print(f"  2. ✓ User signed up (Features 2, 3)")
    
    # Verify email (Feature 8)
    auth_manager.verification_manager.verify_email(verification_token, db_session)
    db_session.commit()
    print(f"  3. ✓ Email verified (Feature 8)")
    
    # Assign role (Feature 6)
    rbac.assign_role(user, "user", db_session)
    db_session.refresh(user)
    print(f"  4. ✓ Role assigned (Feature 6)")
    
    # Login (Feature 3 + 4 + 5)
    tokens = auth_manager.login(email, password, db=db_session)
    access_token = tokens["access_token"]
    refresh_token = tokens["refresh_token"]
    print(f"  5. ✓ Login successful (Features 3, 4, 5)")
    
    # Decode JWT (Feature 4)
    claims = irene_auth.jwt_decode(
        access_token,
        auth_manager.config.secret_key.encode(),
        ["HS256"]
    )
    print(f"  6. ✓ JWT decoded (Feature 4)")
    
    # Refresh access token (Feature 5)
    new_access = auth_manager.refresh_access_token(refresh_token, db_session)
    print(f"  7. ✓ Access token refreshed (Feature 5)")
    
    # Request password reset (Feature 7)
    reset_token = auth_manager.reset_manager.request_password_reset(email, db_session)
    print(f"  8. ✓ Password reset requested (Feature 7)")
    
    # Complete reset (Feature 7)
    new_password = "NewIntegrationPass456!"
    auth_manager.reset_manager.reset_password(reset_token, new_password, db_session)
    print(f"  9. ✓ Password reset completed (Feature 7)")
    
    # Login with new password
    final_tokens = auth_manager.login(email, new_password, db=db_session)
    print(f"  10. ✓ Login with new password (Feature 3)")
    
    # Revoke old refresh token (Feature 5)
    auth_manager.refresh_manager.revoke_token(refresh_token, db_session)
    print(f"  11. ✓ Old refresh token revoked (Feature 5)")
    
    print("  ✅ Complete Workflow: PASS\n")


# ============================================================================
# Run All Tests
# ============================================================================

if __name__ == "__main__":
    print("\n" + "="*70)
    print("IRENE AUTH - COMPREHENSIVE FEATURE TESTS")
    print("="*70)
    
    # Run tests using pytest
    pytest.main([__file__, "-v", "-s"])
