#!/usr/bin/env python3
"""
Novyx Core - Authentication & Authorization (Phase 9)
========================================================
JWT-based authentication with role-based access control for multi-tenant systems.

Features:
- JWT token generation and validation
- Password hashing with bcrypt
- Tenant-based user management
- Role-based permissions (admin, editor, viewer)

Usage:
    from tools.auth import create_access_token, verify_token, hash_password
    
    # Generate JWT
    token = create_access_token(
        tenant_id="acme-corp",
        user_email="admin@acme.com",
        role="admin"
    )
    
    # Verify JWT
    payload = verify_token(token)
    
    # Hash password
    hashed = hash_password("secret123")
    verified = verify_password("secret123", hashed)
"""

import os
import json
from datetime import datetime, timedelta, timezone
from typing import Optional, Dict, Any, List
from pathlib import Path

# JWT and password hashing
try:
    import jwt
    from passlib.context import CryptContext
    AUTH_AVAILABLE = True
except ImportError:
    AUTH_AVAILABLE = False
    print("⚠️  Auth dependencies not available. Install with: pip install pyjwt 'passlib[bcrypt]'")

# Configuration
SECRET_KEY = os.getenv("NOVYX_JWT_SECRET", "novyx-secret-change-in-production-" + "x" * 32)
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("NOVYX_JWT_EXPIRE_MINUTES", "60"))

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


# Role hierarchy (higher number = more permissions)
ROLE_HIERARCHY = {
    "viewer": 1,
    "editor": 2,
    "admin": 3
}


def hash_password(password: str) -> str:
    """
    Hash a password using bcrypt.
    
    Args:
        password: Plain text password
    
    Returns:
        Hashed password string
    
    Example:
        >>> hashed = hash_password("secret123")
        >>> len(hashed) > 50
        True
    """
    if not AUTH_AVAILABLE:
        raise RuntimeError("Auth dependencies not installed")
    return pwd_context.hash(password)


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """
    Verify a password against its hash.
    
    Args:
        plain_password: Plain text password to verify
        hashed_password: Previously hashed password
    
    Returns:
        True if password matches, False otherwise
    
    Example:
        >>> hashed = hash_password("secret123")
        >>> verify_password("secret123", hashed)
        True
        >>> verify_password("wrong", hashed)
        False
    """
    if not AUTH_AVAILABLE:
        raise RuntimeError("Auth dependencies not installed")
    return pwd_context.verify(plain_password, hashed_password)


def create_access_token(
    tenant_id: str,
    user_email: str,
    role: str,
    expires_delta: Optional[timedelta] = None
) -> str:
    """
    Create a JWT access token for a user.
    
    Args:
        tenant_id: Tenant identifier
        user_email: User's email address
        role: User's role (admin, editor, viewer)
        expires_delta: Optional custom expiration time
    
    Returns:
        JWT token string
    
    Example:
        >>> token = create_access_token(
        ...     tenant_id="acme-corp",
        ...     user_email="admin@acme.com",
        ...     role="admin"
        ... )
        >>> len(token) > 100
        True
    """
    if not AUTH_AVAILABLE:
        raise RuntimeError("Auth dependencies not installed")
    
    if expires_delta:
        expire = datetime.now(timezone.utc) + expires_delta
    else:
        expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    
    to_encode = {
        "sub": user_email,
        "tenant_id": tenant_id,
        "role": role,
        "exp": expire,
        "iat": datetime.now(timezone.utc)
    }
    
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt


def verify_token(token: str) -> Optional[Dict[str, Any]]:
    """
    Verify and decode a JWT token.
    
    Args:
        token: JWT token string
    
    Returns:
        Decoded payload dict, or None if invalid
    
    Example:
        >>> token = create_access_token("acme-corp", "user@acme.com", "admin")
        >>> payload = verify_token(token)
        >>> payload["tenant_id"]
        'acme-corp'
    """
    if not AUTH_AVAILABLE:
        raise RuntimeError("Auth dependencies not installed")
    
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None


def has_permission(user_role: str, required_role: str) -> bool:
    """
    Check if a user's role meets the required permission level.
    
    Args:
        user_role: User's role (admin, editor, viewer)
        required_role: Required minimum role
    
    Returns:
        True if user has sufficient permissions
    
    Example:
        >>> has_permission("admin", "editor")
        True
        >>> has_permission("viewer", "editor")
        False
    """
    user_level = ROLE_HIERARCHY.get(user_role.lower(), 0)
    required_level = ROLE_HIERARCHY.get(required_role.lower(), 0)
    return user_level >= required_level


def load_tenant(tenant_id: str) -> Optional[Dict[str, Any]]:
    """
    Load a tenant artifact from the filesystem.
    
    Args:
        tenant_id: Tenant identifier
    
    Returns:
        Tenant artifact dict, or None if not found
    
    Example:
        >>> tenant = load_tenant("acme-corp")
        >>> tenant["tenant_id"] if tenant else None
        'acme-corp'
    """
    tenant_dir = Path("memory/tenants")
    
    # Search for tenant artifact
    for tenant_file in tenant_dir.rglob("*.jsonld"):
        try:
            with open(tenant_file, 'r') as f:
                data = json.load(f)
                if data.get("tenant_id") == tenant_id:
                    return data
        except Exception:
            continue
    
    return None


def authenticate_user(tenant_id: str, email: str, password: str) -> Optional[Dict[str, Any]]:
    """
    Authenticate a user against tenant records using bcrypt password verification.

    Args:
        tenant_id: Tenant identifier
        email: User's email
        password: User's password (plain text)

    Returns:
        User dict with role, or None if authentication fails

    Security:
        - Requires password_hash field in user record
        - Uses bcrypt verification (no plaintext comparison)
        - Returns None for missing/invalid credentials (constant-time)

    Example:
        >>> # Assuming tenant exists with user records and password_hash
        >>> user = authenticate_user("acme-corp", "admin@acme.com", "password123")
        >>> user["role"] if user else None
        'admin'
    """
    if not AUTH_AVAILABLE:
        return None

    tenant = load_tenant(tenant_id)
    if not tenant:
        return None

    # Check users list
    users = tenant.get("users", [])
    for user in users:
        if user.get("email") == email:
            # Require password_hash - reject users without stored hash
            stored_hash = user.get("password_hash")
            if not stored_hash:
                return None  # No password configured for user

            # Verify password using bcrypt
            if not verify_password(password, stored_hash):
                return None  # Password mismatch

            return {
                "email": email,
                "role": user.get("role", "viewer"),
                "name": user.get("name", email),
                "tenant_id": tenant_id
            }

    return None


def get_user_tenants(email: str) -> List[str]:
    """
    Get list of tenant IDs a user has access to.
    
    Args:
        email: User's email address
    
    Returns:
        List of tenant IDs
    
    Example:
        >>> tenants = get_user_tenants("admin@acme.com")
        >>> "acme-corp" in tenants
        True
    """
    tenant_dir = Path("memory/tenants")
    user_tenants = []
    
    for tenant_file in tenant_dir.rglob("*.jsonld"):
        try:
            with open(tenant_file, 'r') as f:
                data = json.load(f)
                users = data.get("users", [])
                for user in users:
                    if user.get("email") == email:
                        user_tenants.append(data.get("tenant_id"))
                        break
        except Exception:
            continue
    
    return user_tenants


if __name__ == "__main__":
    # CLI for testing auth functionality
    import sys
    
    if not AUTH_AVAILABLE:
        print("❌ Auth dependencies not installed")
        sys.exit(1)
    
    print("🔐 Novyx Auth Module Test")
    print("=" * 50)
    
    # Test password hashing
    password = "test123"
    hashed = hash_password(password)
    print(f"✅ Password hashed: {hashed[:50]}...")
    print(f"✅ Verification: {verify_password(password, hashed)}")
    
    # Test JWT creation
    token = create_access_token(
        tenant_id="test-tenant",
        user_email="test@example.com",
        role="admin"
    )
    print(f"\n✅ JWT created: {token[:50]}...")
    
    # Test JWT verification
    payload = verify_token(token)
    if payload:
        print(f"✅ JWT verified:")
        print(f"   Tenant: {payload['tenant_id']}")
        print(f"   User: {payload['sub']}")
        print(f"   Role: {payload['role']}")
    
    # Test permissions
    print(f"\n✅ Permission checks:")
    print(f"   admin -> editor: {has_permission('admin', 'editor')}")
    print(f"   viewer -> editor: {has_permission('viewer', 'editor')}")
