"""
Main authentication manager - Features 2, 3

Implements signup and login workflows using Rust crypto primitives.
"""

from datetime import datetime, timedelta
from typing import Dict, Optional, Tuple
from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker
from email_validator import validate_email, EmailNotValidError

from .config import AuthConfig
from .models import Base, User, RefreshToken
from .jwt_manager import JWTManager
from .refresh_tokens import RefreshTokenManager
from .password_reset import PasswordResetManager
from .email_verification import EmailVerificationManager

# Import Rust crypto functions
try:
    from .irene_auth import hash_password, verify_password
except ImportError:
    # Fallback for development/testing
    import irene_auth as _rust
    hash_password = _rust.hash_password
    verify_password = _rust.verify_password


class AuthenticationError(Exception):
    """Base exception for authentication errors"""
    pass


class InvalidCredentialsError(AuthenticationError):
    """Raised when credentials are invalid"""
    pass


class EmailAlreadyExistsError(AuthenticationError):
    """Raised when email is already registered"""
    pass


class UserNotVerifiedError(AuthenticationError):
    """Raised when user tries to login but email not verified"""
    pass


class UserInactiveError(AuthenticationError):
    """Raised when user account is inactive"""
    pass


class AuthManager:
    """
    Main authentication manager
    
    Coordinates all authentication features:
    - Feature 2: Password hashing (Argon2id via Rust)
    - Feature 3: Signup + Login workflows
    - Feature 4: JWT access token generation
    - Feature 5: Refresh token management
    - Feature 7: Password reset
    - Feature 8: Email verification
    
    Example:
        >>> config = AuthConfig(
        ...     database_url="sqlite:///./auth.db",
        ...     secret_key="your-secret-key-min-32-chars"
        ... )
        >>> auth = AuthManager(config)
        >>> 
        >>> # Signup
        >>> user = auth.signup("user@example.com", "secure_password")
        >>> 
        >>> # Login
        >>> tokens = auth.login("user@example.com", "secure_password")
        >>> print(tokens["access_token"])
    """
    
    def __init__(self, config: AuthConfig):
        """
        Initialize authentication manager
        
        Args:
            config: Authentication configuration
        """
        self.config = config
        
        # Setup database
        self.engine = create_engine(config.database_url)
        self.SessionLocal = sessionmaker(bind=self.engine)
        
        # Create tables
        Base.metadata.create_all(self.engine)
        
        # Initialize sub-managers
        self.jwt_manager = JWTManager(config)
        self.refresh_manager = RefreshTokenManager(config)
        self.reset_manager = PasswordResetManager(config)
        self.verification_manager = EmailVerificationManager(config)
    
    def get_session(self) -> Session:
        """Get database session"""
        return self.SessionLocal()
    
    def signup(
        self,
        email: str,
        password: str,
        require_verification: bool = True,
        db: Optional[Session] = None
    ) -> Tuple[User, Optional[str]]:
        """
        Register a new user - Feature 3 (Signup)
        
        Args:
            email: User's email address
            password: User's plaintext password (will be hashed with Argon2id)
            require_verification: Whether to generate verification token
            db: Optional database session (creates new if not provided)
        
        Returns:
            Tuple of (User object, verification_token or None)
        
        Raises:
            EmailAlreadyExistsError: If email is already registered
            ValueError: If email or password is invalid
        
        Security:
            - Password is hashed with Argon2id (Rust)
            - Verification token is generated with CSPRNG (Rust)
            - Token hash stored in DB, plaintext sent to user
        
        Example:
            >>> user, token = auth.signup("user@example.com", "MyPass123!")
            >>> # Send verification email with token
            >>> send_email(user.email, f"Verify: {token}")
        """
        close_session = db is None
        if db is None:
            db = self.get_session()
        
        try:
            # Validate email
            try:
                validated = validate_email(email, check_deliverability=False)
                email = validated.normalized
            except EmailNotValidError as e:
                raise ValueError(f"Invalid email: {e}")
            
            # Validate password
            if len(password) < 8:
                raise ValueError("Password must be at least 8 characters")
            
            # Check if email exists
            existing = db.query(User).filter_by(email=email).first()
            if existing:
                raise EmailAlreadyExistsError(f"Email {email} is already registered")
            
            # Hash password using Rust Argon2id
            password_hash = hash_password(password)
            
            # Create user
            user = User(
                email=email,
                password_hash=password_hash,
                is_active=True,
                is_verified=not require_verification  # Auto-verify if not required
            )
            
            # Generate verification token if required
            verification_token = None
            if require_verification:
                verification_token = self.verification_manager.create_verification_token(user, db)
            
            db.add(user)
            db.commit()
            db.refresh(user)
            
            return user, verification_token
        
        finally:
            if close_session:
                db.close()
    
    def login(
        self,
        email: str,
        password: str,
        require_verified: bool = True,
        db: Optional[Session] = None
    ) -> Dict[str, str]:
        """
        Authenticate user and generate tokens - Feature 3 (Login)
        
        Args:
            email: User's email address
            password: User's plaintext password
            require_verified: Whether to require email verification
            db: Optional database session
        
        Returns:
            Dictionary with access_token and refresh_token
        
        Raises:
            InvalidCredentialsError: If email/password is wrong
            UserNotVerifiedError: If email not verified (when required)
            UserInactiveError: If account is inactive
        
        Security:
            - Password verification uses constant-time comparison (Rust)
            - JWT signed with HS256 or RS256
            - Refresh token hashed before storage
        
        Example:
            >>> tokens = auth.login("user@example.com", "MyPass123!")
            >>> access_token = tokens["access_token"]
            >>> refresh_token = tokens["refresh_token"]
            >>> # Use access_token for API requests
        """
        close_session = db is None
        if db is None:
            db = self.get_session()
        
        try:
            # Find user
            user = db.query(User).filter_by(email=email).first()
            if not user:
                raise InvalidCredentialsError("Invalid email or password")
            
            # Verify password using Rust constant-time comparison
            try:
                verify_password(password, user.password_hash)
            except Exception:
                raise InvalidCredentialsError("Invalid email or password")
            
            # Check if verified
            if require_verified and not user.is_verified:
                raise UserNotVerifiedError("Email address not verified")
            
            # Check if active
            if not user.is_active:
                raise UserInactiveError("Account is inactive")
            
            # Generate tokens
            access_token = self.jwt_manager.create_access_token(user)
            refresh_token = self.refresh_manager.create_refresh_token(user, db)
            
            return {
                "access_token": access_token,
                "refresh_token": refresh_token,
                "token_type": "bearer"
            }
        
        finally:
            if close_session:
                db.close()
    
    def refresh_access_token(
        self,
        refresh_token: str,
        db: Optional[Session] = None
    ) -> str:
        """
        Generate new access token from refresh token - Feature 5
        
        Args:
            refresh_token: Valid refresh token
            db: Optional database session
        
        Returns:
            New access token
        
        Raises:
            InvalidCredentialsError: If refresh token is invalid/expired/revoked
        """
        close_session = db is None
        if db is None:
            db = self.get_session()
        
        try:
            user = self.refresh_manager.verify_refresh_token(refresh_token, db)
            return self.jwt_manager.create_access_token(user)
        finally:
            if close_session:
                db.close()
