from fastapi import Depends, HTTPException, status, Request, Cookie
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from sqlalchemy.orm import Session
from typing import Optional
from datetime import datetime
import os
from ...shared.config.database import get_db
from ...shared.config.settings import settings
from ...auth.models.user import User
from ...auth.models.role import Role
security = HTTPBearer(auto_error=False)

def get_jwt_secret() -> str:
    """
    Get JWT secret securely, fail if not configured.
    Never use hardcoded fallback secrets.
    """
    # Remove default secret entirely - fail fast if not configured
    jwt_secret = getattr(settings, 'JWT_SECRET', None) or os.getenv('JWT_SECRET', None)
    
    # Fail fast if secret is not configured
    if not jwt_secret:
        error_msg = (
            'CRITICAL: JWT_SECRET is not configured. '
            'Please set JWT_SECRET environment variable to a secure random string (minimum 32 characters).'
        )
        import logging
        logger = logging.getLogger(__name__)
        logger.error(error_msg)
        if settings.is_production:
            raise ValueError(error_msg)
        else:
            # In development, generate a secure secret but warn
            import secrets
            jwt_secret = secrets.token_urlsafe(64)
            logger.warning(
                f'JWT_SECRET not configured. Auto-generated secret for development. '
                f'Set JWT_SECRET environment variable for production: {jwt_secret}'
            )
    
    # Validate JWT secret strength
    if len(jwt_secret) < 32:
        error_msg = 'JWT_SECRET must be at least 32 characters long for security.'
        import logging
        logger = logging.getLogger(__name__)
        logger.error(error_msg)
        if settings.is_production:
            raise ValueError(error_msg)
        else:
            logger.warning(error_msg)
    
    return jwt_secret

def get_current_user(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    access_token: Optional[str] = Cookie(None, alias='accessToken'),
    db: Session = Depends(get_db)
) -> User:
    """
    Get current user from either Authorization header or httpOnly cookie.
    Prefers Authorization header for backward compatibility, falls back to cookie.
    """
    # Try to get token from Authorization header first
    token = None
    if credentials:
        token = credentials.credentials
    
    # Fall back to cookie if no header token
    if not token and access_token:
        token = access_token
    
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail='Could not validate credentials',
        headers={'WWW-Authenticate': 'Bearer'}
    )
    
    if not token:
        raise credentials_exception
    
    try:
        jwt_secret = get_jwt_secret()
        payload = jwt.decode(token, jwt_secret, algorithms=['HS256'])
        user_id: int = payload.get('userId')
        if user_id is None:
            raise credentials_exception
    except JWTError:
        raise credentials_exception
    except ValueError as e:
        # JWT secret configuration error - should not happen in production
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Server configuration error')
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        raise credentials_exception
    
    # Check if user account is active
    if not user.is_active:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail='Account is disabled. Please contact support.'
        )
    
    # Check if account is locked
    if user.locked_until and user.locked_until > datetime.utcnow():
        remaining_minutes = int((user.locked_until - datetime.utcnow()).total_seconds() / 60)
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=f'Account is temporarily locked due to multiple failed login attempts. Please try again in {remaining_minutes} minute(s).'
        )
    
    return user

def authorize_roles(*allowed_roles: str):

    def role_checker(current_user: User=Depends(get_current_user), db: Session=Depends(get_db)) -> User:
        role = db.query(Role).filter(Role.id == current_user.role_id).first()
        if not role:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='User role not found')
        user_role_name = role.name
        if user_role_name not in allowed_roles:
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail='You do not have permission to access this resource')
        return current_user
    return role_checker

def get_current_user_optional(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer(auto_error=False)),
    access_token: Optional[str] = Cookie(None, alias='accessToken'),
    db: Session = Depends(get_db)
) -> Optional[User]:
    """
    Get current user optionally from either Authorization header or httpOnly cookie.
    Returns None if no valid token is found, or if user is inactive/locked.
    This ensures inactive/locked users are never considered "authenticated" even for optional features.
    """
    # Try to get token from Authorization header first
    token = None
    if credentials:
        token = credentials.credentials
    
    # Fall back to cookie if no header token
    if not token and access_token:
        token = access_token
    
    if not token:
        return None
    
    try:
        jwt_secret = get_jwt_secret()
        payload = jwt.decode(token, jwt_secret, algorithms=['HS256'])
        user_id: int = payload.get('userId')
        if user_id is None:
            return None
    except (JWTError, ValueError):
        return None
    
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        return None
    
    # Check if user account is active - return None for inactive users
    if not user.is_active:
        return None
    
    # Check if account is locked - return None for locked users
    if user.locked_until and user.locked_until > datetime.utcnow():
        return None
    
    return user

def verify_token(token: str) -> dict:
    jwt_secret = get_jwt_secret()
    payload = jwt.decode(token, jwt_secret, algorithms=['HS256'])
    return payload