"""
Refresh token management - Feature 5

Handles refresh token creation, validation, and revocation.
"""

from datetime import datetime, timedelta
from typing import Optional
from sqlalchemy.orm import Session

from .config import AuthConfig
from .models import User, RefreshToken

# Import Rust token functions
try:
    from .irene_auth import generate_token, hash_token, verify_token_hash
except ImportError:
    import irene_auth as _rust
    generate_token = _rust.generate_token
    hash_token = _rust.hash_token
    verify_token_hash = _rust.verify_token_hash


class RefreshTokenManager:
    """
    Refresh token manager - Feature 5
    
    Manages long-lived refresh tokens for obtaining new access tokens.
    """
    
    def __init__(self, config: AuthConfig):
        self.config = config
    
    def create_refresh_token(self, user: User, db: Session) -> str:
        """
        Create refresh token for user
        
        Args:
            user: User object
            db: Database session
        
        Returns:
            Plaintext refresh token (send to user, don't store!)
        
        Security:
            - Token generated with CSPRNG (Rust)
            - Only hash stored in database
            - Use verify_refresh_token() with constant-time comparison
        """
        # Generate secure random token (Rust)
        token = generate_token(32)
        
        # Hash for storage (Rust SHA-256)
        token_hash = hash_token(token)
        
        # Calculate expiration
        expires_at = datetime.utcnow() + timedelta(days=self.config.refresh_token_expire_days)
        
        # Store in database
        refresh_token = RefreshToken(
            token_hash=token_hash,
            user_id=user.id,
            expires_at=expires_at
        )
        db.add(refresh_token)
        db.commit()
        
        return token
    
    def verify_refresh_token(self, token: str, db: Session) -> User:
        """
        Verify refresh token and return associated user
        
        Args:
            token: Plaintext refresh token
            db: Database session
        
        Returns:
            User object if token is valid
        
        Raises:
            ValueError: If token is invalid, expired, or revoked
        
        Security:
            - Uses constant-time comparison (Rust)
            - Checks expiration and revocation
        """
        # Get all valid refresh tokens
        tokens = db.query(RefreshToken).filter_by(revoked=False).all()
        
        # Find matching token using constant-time comparison (Rust)
        for refresh_token in tokens:
            if verify_token_hash(token, refresh_token.token_hash):
                # Check expiration
                if not refresh_token.is_valid():
                    raise ValueError("Refresh token has expired")
                
                return refresh_token.user
        
        raise ValueError("Invalid refresh token")
    
    def revoke_token(self, token: str, db: Session) -> bool:
        """
        Revoke a refresh token
        
        Args:
            token: Plaintext refresh token to revoke
            db: Database session
        
        Returns:
            True if token was revoked, False if not found
        """
        tokens = db.query(RefreshToken).filter_by(revoked=False).all()
        
        for refresh_token in tokens:
            if verify_token_hash(token, refresh_token.token_hash):
                refresh_token.revoke()
                db.commit()
                return True
        
        return False
    
    def revoke_all_user_tokens(self, user_id: int, db: Session) -> int:
        """
        Revoke all refresh tokens for a user
        
        Args:
            user_id: User ID
            db: Database session
        
        Returns:
            Number of tokens revoked
        """
        tokens = db.query(RefreshToken).filter_by(
            user_id=user_id,
            revoked=False
        ).all()
        
        count = 0
        for token in tokens:
            token.revoke()
            count += 1
        
        db.commit()
        return count
