from passlib.context import CryptContext
from jose import JWTError, jwt
from datetime import datetime, timedelta
from typing import Optional, Union
from fastapi import HTTPException, status, Depends, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
import os
import secrets
import hashlib
import json
import logging

# Setup logger
logger = logging.getLogger("vfx_auth")

# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

# JWT settings
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-here-change-in-production")
REFRESH_SECRET_KEY = os.getenv("REFRESH_SECRET_KEY", "your-refresh-secret-key-here-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_DAYS = 7

# Security scheme
security = HTTPBearer()

# API Key settings
API_KEY_PREFIX = "vfx_"
API_KEY_LENGTH = 32


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """Verify a plain password against its hash."""
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    """Hash a password using bcrypt."""
    return pwd_context.hash(password)


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """Create a JWT access token."""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    
    to_encode.update({"exp": expire, "type": "access"})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def create_refresh_token(data: dict, expires_delta: Optional[timedelta] = None):
    """Create a JWT refresh token."""
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
    
    to_encode.update({"exp": expire, "type": "refresh"})
    encoded_jwt = jwt.encode(to_encode, REFRESH_SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def verify_token(token: str, token_type: str = "access") -> Optional[dict]:
    """Verify and decode a JWT token."""
    try:
        secret_key = SECRET_KEY if token_type == "access" else REFRESH_SECRET_KEY
        logger.debug(f"🔐 Decoding {token_type} token with secret: {secret_key[:10]}...")
        
        payload = jwt.decode(token, secret_key, algorithms=[ALGORITHM])
        logger.debug(f"🔐 Token decoded successfully: {payload}")
        
        # Verify token type
        if payload.get("type") != token_type:
            logger.warning(f"🔐 Token type mismatch: expected {token_type}, got {payload.get('type')}")
            return None
            
        return payload
    except JWTError as e:
        logger.warning(f"🔐 JWT decode error: {e}")
        return None


def get_current_user_from_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    """Extract user information from JWT token."""
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    try:
        logger.debug(f"🔐 Verifying token: {credentials.credentials[:20]}...")
        payload = verify_token(credentials.credentials, "access")
        if payload is None:
            logger.warning("🔐 Token verification failed - invalid token")
            raise credentials_exception
            
        logger.debug(f"🔐 Token payload: {payload}")
        
        user_id_str = payload.get("sub")
        if user_id_str is None:
            logger.warning("🔐 Token verification failed - no sub field")
            raise credentials_exception
        
        try:
            user_id = int(user_id_str)
            logger.debug(f"🔐 Extracted user_id: {user_id}")
        except (ValueError, TypeError):
            logger.warning(f"🔐 Token verification failed - invalid user_id: {user_id_str}")
            raise credentials_exception
            
        return {"user_id": user_id, "email": payload.get("email")}
    except HTTPException:
        raise
    except Exception as e:
        logger.error(f"🔐 Token verification error: {e}")
        raise credentials_exception





def get_current_user(
    token_data: dict = Depends(get_current_user_from_token),
    db: Session = Depends(lambda: None)
):
    """Get current user from database using token data."""
    from database import get_db
    
    # Get database session if not provided
    if db is None:
        db_gen = get_db()
        db = next(db_gen)
        try:
            return _get_user_from_db(db, token_data["user_id"])
        finally:
            db.close()
    else:
        return _get_user_from_db(db, token_data["user_id"])


def _get_user_from_db(db: Session, user_id: int):
    """Helper function to get user from database."""
    from models.user import User
    
    logger.debug(f"🔐 Looking up user_id: {user_id}")
    user = db.query(User).filter(User.id == user_id).first()
    if user is None:
        logger.warning(f"🔐 User not found: {user_id}")
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="User not found"
        )
    
    logger.debug(f"🔐 Found user: {user.email} (approved: {user.is_approved})")
    if not user.is_approved:
        logger.warning(f"🔐 User not approved: {user.email}")
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="User account not approved"
        )
    
    return user


def get_current_user_with_db(
    token_data: dict = Depends(get_current_user_from_token),
    db: Session = Depends(lambda: None)
):
    """Get current user with database dependency injection."""
    from database import get_db
    
    if db is None:
        # This should not happen in normal FastAPI usage
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Database session not available"
        )
    
    return _get_user_from_db(db, token_data["user_id"])


def require_role(required_roles: list):
    """Decorator to require specific user roles."""
    def role_checker(
        token_data: dict = Depends(get_current_user_from_token),
        db: Session = Depends(lambda: None)
    ):
        from database import get_db
        
        # Get database session if not provided
        if db is None:
            db_gen = get_db()
            db = next(db_gen)
            try:
                current_user = _get_user_from_db(db, token_data["user_id"])
            finally:
                db.close()
        else:
            current_user = _get_user_from_db(db, token_data["user_id"])
        
        if current_user.role not in required_roles:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Insufficient permissions"
            )
        return current_user
    return role_checker


def require_admin_permission():
    """Decorator to require admin permission regardless of role."""
    def admin_checker(
        token_data: dict = Depends(get_current_user_from_token),
        db: Session = Depends(lambda: None)
    ):
        from database import get_db
        
        # Get database session if not provided
        if db is None:
            db_gen = get_db()
            db = next(db_gen)
            try:
                current_user = _get_user_from_db(db, token_data["user_id"])
            finally:
                db.close()
        else:
            current_user = _get_user_from_db(db, token_data["user_id"])
        
        if not current_user.is_admin:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Admin permission required"
            )
        return current_user
    return admin_checker


def create_role_dependency(required_roles: list):
    """Create a dependency that requires specific user roles with proper DB injection."""
    def role_checker(
        token_data: dict = Depends(get_current_user_from_token),
        db: Session = None
    ):
        from database import get_db
        
        # Get database session if not provided
        if db is None:
            db_gen = get_db()
            db = next(db_gen)
            try:
                current_user = _get_user_from_db(db, token_data["user_id"])
            finally:
                db.close()
        else:
            current_user = _get_user_from_db(db, token_data["user_id"])
        
        if current_user.role not in required_roles:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Insufficient permissions"
            )
        return current_user
    return role_checker


# API Key utilities
def generate_api_key() -> str:
    """Generate a new API key."""
    random_part = secrets.token_urlsafe(API_KEY_LENGTH)
    return f"{API_KEY_PREFIX}{random_part}"


def hash_api_key(api_key: str) -> str:
    """Hash an API key for secure storage."""
    return hashlib.sha256(api_key.encode()).hexdigest()


def verify_api_key_format(api_key: str) -> bool:
    """Verify that an API key has the correct format."""
    return api_key.startswith(API_KEY_PREFIX) and len(api_key) > len(API_KEY_PREFIX)


def get_current_user_from_api_key(
    request: Request,
    db: Session = Depends(lambda: None)
) -> Optional[dict]:
    """Extract user information from API key."""
    from database import get_db
    from models.api_key import APIKey
    from models.api_key_usage import APIKeyUsage
    
    # Get API key from header
    api_key = request.headers.get("X-API-Key")
    if not api_key:
        return None
    
    # Verify format
    if not verify_api_key_format(api_key):
        return None
    
    # Get database session if not provided
    if db is None:
        db_gen = get_db()
        db = next(db_gen)
        try:
            return _verify_api_key_and_get_user(db, api_key, request)
        finally:
            db.close()
    else:
        return _verify_api_key_and_get_user(db, api_key, request)


def _verify_api_key_and_get_user(db: Session, api_key: str, request: Request) -> Optional[dict]:
    """Helper function to verify API key and get user."""
    from models.api_key import APIKey
    from models.api_key_usage import APIKeyUsage
    from models.user import User
    
    # Hash the provided key
    key_hash = hash_api_key(api_key)
    
    # Find the API key in database
    api_key_record = db.query(APIKey).filter(
        APIKey.key_hash == key_hash,
        APIKey.is_active == True
    ).first()
    
    if not api_key_record:
        return None
    
    # Check if key is expired
    if api_key_record.expires_at and api_key_record.expires_at < datetime.utcnow():
        return None
    
    # Get the user
    user = db.query(User).filter(User.id == api_key_record.user_id).first()
    if not user or not user.is_approved:
        return None
    
    # Log API key usage
    usage_log = APIKeyUsage(
        api_key_id=api_key_record.id,
        endpoint=str(request.url.path),
        method=request.method,
        ip_address=request.client.host if request.client else None,
        user_agent=request.headers.get("User-Agent")
    )
    db.add(usage_log)
    
    # Update last used timestamp
    api_key_record.last_used_at = datetime.utcnow()
    db.commit()
    
    # Parse scopes
    try:
        scopes = json.loads(api_key_record.scopes)
    except json.JSONDecodeError:
        scopes = []
    
    return {
        "user_id": user.id,
        "email": user.email,
        "role": user.role,
        "api_key_id": api_key_record.id,
        "scopes": scopes,
        "auth_type": "api_key"
    }


def get_current_user_flexible(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
    db: Session = Depends(lambda: None)
):
    """Get current user from either JWT token or API key."""
    from database import get_db
    
    # Get database session if not provided
    if db is None:
        db_gen = get_db()
        db = next(db_gen)
        try:
            return _get_current_user_flexible_with_db(request, credentials, db)
        finally:
            db.close()
    else:
        return _get_current_user_flexible_with_db(request, credentials, db)


def _get_current_user_flexible_with_db(
    request: Request,
    credentials: Optional[HTTPAuthorizationCredentials],
    db: Session
):
    """Helper function for flexible authentication with database session."""
    # Try API key first
    api_key_user = get_current_user_from_api_key(request, db)
    if api_key_user:
        # Get full user object
        user = _get_user_from_db(db, api_key_user["user_id"])
        # Add API key specific data
        user.api_key_id = api_key_user["api_key_id"]
        user.scopes = api_key_user["scopes"]
        user.auth_type = "api_key"
        return user
    
    # Try JWT token
    if credentials:
        try:
            payload = verify_token(credentials.credentials, "access")
            if payload:
                user = _get_user_from_db(db, payload.get("sub"))
                user.auth_type = "jwt"
                return user
        except Exception:
            pass
    
    # No valid authentication found
    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )


def check_api_key_scope(user, required_scope: str) -> bool:
    """Check if the current user (authenticated via API key) has the required scope."""
    if not hasattr(user, 'auth_type') or user.auth_type != 'api_key':
        # JWT tokens have full access based on user role
        return True
    
    if not hasattr(user, 'scopes'):
        return False
    
    # Check if user has the specific scope or full access
    return required_scope in user.scopes or "full:access" in user.scopes


def require_api_key_scope(required_scope: str):
    """Decorator to require specific API key scope."""
    def scope_checker(
        request: Request,
        credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
        db: Session = Depends(lambda: None)
    ):
        from database import get_db
        
        if db is None:
            db_gen = get_db()
            db = next(db_gen)
            try:
                user = _get_current_user_flexible_with_db(request, credentials, db)
            finally:
                db.close()
        else:
            user = _get_current_user_flexible_with_db(request, credentials, db)
        
        if not check_api_key_scope(user, required_scope):
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail=f"Insufficient permissions. Required scope: {required_scope}"
            )
        return user
    return scope_checker