"""
Complete FastAPI Application with Irene Auth

Demonstrates ALL 8 authentication features:
1. User Model (SQLAlchemy) ✅
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 ✅

Run with: uvicorn fastapi_app:app --reload
"""

from typing import Optional
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session

from irene_auth import (
    AuthManager,
    AuthConfig,
    User,
    RBACManager,
    create_auth_dependencies
)

# ============================================================================
# Configuration
# ============================================================================

config = AuthConfig(
    database_url="sqlite:///./auth_example.db",
    secret_key="your-super-secret-key-min-32-chars-CHANGE-THIS-IN-PRODUCTION",
    algorithm="HS256",
    access_token_expire_minutes=15,
    refresh_token_expire_days=30,
)

auth = AuthManager(config)
rbac = RBACManager()

# Create default roles
with auth.get_session() as db:
    try:
        rbac.create_role("admin", "Administrator with full access", db)
        rbac.create_role("user", "Regular user", db)
        rbac.create_role("moderator", "Content moderator", db)
    except ValueError:
        pass  # Roles already exist

# FastAPI dependencies
auth_deps = create_auth_dependencies(config, auth.get_session)

app = FastAPI(
    title="Irene Auth Example",
    description="Complete authentication system with all 8 features",
    version="0.1.0"
)

# ============================================================================
# Request/Response Models
# ============================================================================

class SignupRequest(BaseModel):
    email: EmailStr
    password: str

class LoginRequest(BaseModel):
    email: EmailStr
    password: str

class RefreshRequest(BaseModel):
    refresh_token: str

class PasswordResetRequest(BaseModel):
    email: EmailStr

class PasswordResetConfirm(BaseModel):
    token: str
    new_password: str

class EmailVerifyRequest(BaseModel):
    token: str

class RoleAssignRequest(BaseModel):
    user_email: EmailStr
    role_name: str

class TokenResponse(BaseModel):
    access_token: str
    refresh_token: str
    token_type: str = "bearer"

class MessageResponse(BaseModel):
    message: str

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

@app.post("/auth/signup", response_model=MessageResponse, tags=["Authentication"])
async def signup(request: SignupRequest):
    """
    Register a new user (Feature 3: Signup)
    
    - Hashes password with Argon2id (Feature 2)
    - Generates email verification token (Feature 8)
    - Returns verification token (in production, send via email)
    """
    try:
        with auth.get_session() as db:
            user, verification_token = auth.signup(
                email=request.email,
                password=request.password,
                require_verification=True,
                db=db
            )
        
        # In production, send verification_token via email
        print(f"[EMAIL] Verification token for {user.email}: {verification_token}")
        
        return {"message": f"User registered. Verification token: {verification_token}"}
    
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@app.post("/auth/login", response_model=TokenResponse, tags=["Authentication"])
async def login(request: LoginRequest):
    """
    Authenticate user and get tokens (Feature 3: Login)
    
    - Verifies password with constant-time comparison
    - Returns JWT access token (Feature 4)
    - Returns refresh token (Feature 5)
    """
    try:
        with auth.get_session() as db:
            tokens = auth.login(
                email=request.email,
                password=request.password,
                require_verified=False,  # Set to True in production
                db=db
            )
        
        return tokens
    
    except Exception as e:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=str(e),
            headers={"WWW-Authenticate": "Bearer"},
        )


# ============================================================================
# Feature 4: JWT Access Token Protected Endpoints
# ============================================================================

@app.get("/auth/me", tags=["Authentication"])
async def get_current_user_info(user: User = Depends(auth_deps.get_current_user)):
    """
    Get current user information (Feature 4: JWT validation)
    
    Requires: Valid JWT access token in Authorization header
    """
    return {
        "id": user.id,
        "email": user.email,
        "is_verified": user.is_verified,
        "is_active": user.is_active,
        "roles": [role.name for role in user.roles],
        "created_at": user.created_at.isoformat(),
    }


@app.get("/protected", tags=["Protected"])
async def protected_endpoint(user: User = Depends(auth_deps.get_current_active_user)):
    """
    Protected endpoint requiring active user
    
    Requires: Valid JWT + Active account
    """
    return {"message": f"Hello, {user.email}! You have access."}


# ============================================================================
# Feature 5: Refresh Token Endpoints
# ============================================================================

@app.post("/auth/refresh", response_model=dict, tags=["Authentication"])
async def refresh_access_token(request: RefreshRequest):
    """
    Get new access token using refresh token (Feature 5)
    
    - Validates refresh token with constant-time comparison
    - Checks expiration and revocation status
    - Returns new access token
    """
    try:
        with auth.get_session() as db:
            new_access_token = auth.refresh_access_token(request.refresh_token, db)
        
        return {"access_token": new_access_token, "token_type": "bearer"}
    
    except Exception as e:
        raise HTTPException(status_code=401, detail=str(e))


@app.post("/auth/logout", tags=["Authentication"])
async def logout(
    request: RefreshRequest,
    user: User = Depends(auth_deps.get_current_user)
):
    """
    Logout user by revoking refresh token (Feature 5)
    """
    with auth.get_session() as db:
        revoked = auth.refresh_manager.revoke_token(request.refresh_token, db)
    
    if revoked:
        return {"message": "Logged out successfully"}
    else:
        raise HTTPException(status_code=400, detail="Invalid refresh token")


# ============================================================================
# Feature 6: Role-Based Access Control (RBAC)
# ============================================================================

@app.post("/admin/assign-role", tags=["Admin - RBAC"])
async def assign_role(
    request: RoleAssignRequest,
    admin_user: User = Depends(auth_deps.require_role("admin"))
):
    """
    Assign role to user (Feature 6: RBAC)
    
    Requires: Admin role
    """
    with auth.get_session() as db:
        user = db.query(User).filter_by(email=request.user_email).first()
        if not user:
            raise HTTPException(status_code=404, detail="User not found")
        
        try:
            rbac.assign_role(user, request.role_name, db)
            return {"message": f"Role '{request.role_name}' assigned to {user.email}"}
        except ValueError as e:
            raise HTTPException(status_code=400, detail=str(e))


@app.get("/admin/dashboard", tags=["Admin - RBAC"])
async def admin_dashboard(user: User = Depends(auth_deps.require_role("admin"))):
    """
    Admin-only endpoint (Feature 6: RBAC)
    
    Requires: Admin role
    """
    return {
        "message": "Welcome to admin dashboard",
        "admin": user.email,
        "roles": [role.name for role in user.roles]
    }


@app.get("/moderate/content", tags=["Moderation - RBAC"])
async def moderate_content(
    user: User = Depends(auth_deps.require_any_role(["admin", "moderator"]))
):
    """
    Moderation endpoint (Feature 6: RBAC)
    
    Requires: Admin OR Moderator role
    """
    return {
        "message": "You can moderate content",
        "user": user.email,
        "roles": [role.name for role in user.roles]
    }


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

@app.post("/auth/password-reset/request", response_model=MessageResponse, tags=["Password Reset"])
async def request_password_reset(request: PasswordResetRequest):
    """
    Request password reset (Feature 7)
    
    - Generates secure reset token with CSPRNG
    - Stores hashed token in database
    - Returns token (in production, send via email)
    """
    with auth.get_session() as db:
        token = auth.reset_manager.request_password_reset(request.email, db)
    
    if token:
        # In production, send token via email
        print(f"[EMAIL] Password reset token for {request.email}: {token}")
        return {"message": f"Reset token generated: {token}"}
    else:
        # Don't reveal if email exists (prevent enumeration)
        return {"message": "If email exists, reset link sent"}


@app.post("/auth/password-reset/confirm", response_model=MessageResponse, tags=["Password Reset"])
async def confirm_password_reset(request: PasswordResetConfirm):
    """
    Reset password with token (Feature 7)
    
    - Validates token with constant-time comparison
    - Checks expiration
    - Hashes new password with Argon2id
    - Invalidates reset token
    """
    try:
        with auth.get_session() as db:
            success = auth.reset_manager.reset_password(
                token=request.token,
                new_password=request.new_password,
                db=db
            )
        
        if success:
            return {"message": "Password reset successful"}
        else:
            raise HTTPException(status_code=400, detail="Invalid or expired token")
    
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))


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

@app.post("/auth/verify-email", response_model=MessageResponse, tags=["Email Verification"])
async def verify_email(request: EmailVerifyRequest):
    """
    Verify email address (Feature 8)
    
    - Validates verification token with constant-time comparison
    - Checks expiration
    - Marks user as verified
    - Invalidates verification token
    """
    with auth.get_session() as db:
        success = auth.verification_manager.verify_email(request.token, db)
    
    if success:
        return {"message": "Email verified successfully"}
    else:
        raise HTTPException(status_code=400, detail="Invalid or expired verification token")


@app.post("/auth/resend-verification", response_model=MessageResponse, tags=["Email Verification"])
async def resend_verification(request: PasswordResetRequest):
    """
    Resend email verification token (Feature 8)
    
    - Generates new verification token
    - Invalidates old token
    - Returns new token (in production, send via email)
    """
    with auth.get_session() as db:
        token = auth.verification_manager.resend_verification(request.email, db)
    
    if token:
        # In production, send token via email
        print(f"[EMAIL] New verification token for {request.email}: {token}")
        return {"message": f"Verification token sent: {token}"}
    else:
        return {"message": "User not found or already verified"}


# ============================================================================
# Health Check
# ============================================================================

@app.get("/health", tags=["System"])
async def health_check():
    """Health check endpoint"""
    return {
        "status": "healthy",
        "features": [
            "User Model (SQLAlchemy)",
            "Password Hashing (Argon2id)",
            "Signup + Login",
            "JWT Access Tokens",
            "Refresh Tokens",
            "Role-Based Access Control",
            "Password Reset",
            "Email Verification"
        ]
    }


# ============================================================================
# Run Instructions
# ============================================================================

if __name__ == "__main__":
    import uvicorn
    
    print("\n" + "="*60)
    print("🚀 Irene Auth - Complete FastAPI Example")
    print("="*60)
    print("\nAll 8 features implemented:")
    print("  1. ✅ User Model (SQLAlchemy)")
    print("  2. ✅ Password Hashing (Argon2id)")
    print("  3. ✅ Signup + Login")
    print("  4. ✅ JWT Access Tokens")
    print("  5. ✅ Refresh Tokens")
    print("  6. ✅ Role-Based Access Control")
    print("  7. ✅ Password Reset")
    print("  8. ✅ Email Verification")
    print("\nAPI Docs: http://localhost:8000/docs")
    print("="*60 + "\n")
    
    uvicorn.run(app, host="0.0.0.0", port=8000)
