"""
Authentication and authorization module for Toxo.

Handles user authentication, session management, and layer access control.
"""

import jwt
import hashlib
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from dataclasses import dataclass

from ..utils.logger import get_logger
from ..utils.exceptions import SecurityError
from .database import get_database_manager


@dataclass
class User:
    """User information."""
    id: str
    email: str
    name: str
    created_at: str
    subscription: str = "free"


class AuthManager:
    """
    Manages authentication and authorization for Toxo layers.
    
    Uses persistent SQLite database for user storage and session management.
    """
    
    def __init__(self, secret_key: Optional[str] = None, create_demo_users: bool = False):
        self.secret_key = secret_key
        self.logger = get_logger(__name__)
        self.db = get_database_manager()
        if not self.secret_key:
            # Never ship a fixed secret. Require env var or explicit parameter.
            import os
            self.secret_key = os.getenv("TOXO_JWT_SECRET")
        if not self.secret_key:
            raise SecurityError(
                "Missing JWT secret. Set TOXO_JWT_SECRET or pass secret_key explicitly."
            )

        # Create demo users only when explicitly requested.
        if create_demo_users:
            self._ensure_demo_users()
        
        self.logger.info("AuthManager initialized with database backend")
    
    def _ensure_demo_users(self):
        """Ensure demo users exist in the database."""
        try:
            # Check if demo user exists
            if not self.db.get_user_by_email("demo@toxo.ai"):
                self.db.create_user("demo@toxo.ai", "demo123", "Demo User")
                # Update subscription to pro
                conn = self.db._db_manager if hasattr(self.db, '_db_manager') else self.db
                import sqlite3
                db_conn = sqlite3.connect(self.db.db_path)
                cursor = db_conn.cursor()
                cursor.execute('UPDATE users SET subscription = ? WHERE email = ?', ("pro", "demo@toxo.ai"))
                db_conn.commit()
                db_conn.close()
                
            if not self.db.get_user_by_email("m@gmail.com"):
                self.db.create_user("m@gmail.com", "123456", "Main User")
                # Update subscription to pro
                import sqlite3
                db_conn = sqlite3.connect(self.db.db_path)
                cursor = db_conn.cursor()
                cursor.execute('UPDATE users SET subscription = ? WHERE email = ?', ("pro", "m@gmail.com"))
                db_conn.commit()
                db_conn.close()
                
        except Exception as e:
            self.logger.warning(f"Failed to create demo users: {e}")
    
    def create_token(self, user_id: str) -> str:
        """Create a JWT token for a user."""
        try:
            expires_at = datetime.utcnow() + timedelta(days=7)  # 7 day expiry
            payload = {
                "user_id": user_id,
                "exp": expires_at,
                "iat": datetime.utcnow()
            }
            
            token = jwt.encode(payload, self.secret_key, algorithm="HS256")
            
            # Store session in database
            token_hash = hashlib.sha256(token.encode()).hexdigest()
            self.db.create_session(user_id, token_hash, expires_at)
            
            self.logger.info(f"Created token for user {user_id}")
            return token
            
        except Exception as e:
            self.logger.error(f"Failed to create token: {e}")
            raise SecurityError(f"Token creation failed: {e}")
    
    def validate_token(self, token: str) -> Optional[str]:
        """Validate a JWT token and return user_id."""
        try:
            if not token:
                return None
            
            # Verify JWT first
            try:
                payload = jwt.decode(token, self.secret_key, algorithms=["HS256"])
                user_id = payload.get("user_id")
                
                if not user_id:
                    return None
                
                # Check session in database
                token_hash = hashlib.sha256(token.encode()).hexdigest()
                db_user_id = self.db.validate_session(token_hash)
                
                if db_user_id == user_id:
                    return user_id
                    
            except jwt.ExpiredSignatureError:
                # Remove expired token from database
                token_hash = hashlib.sha256(token.encode()).hexdigest()
                self.db.revoke_session(token_hash)
                return None
            except jwt.InvalidTokenError:
                # Remove invalid token from database
                token_hash = hashlib.sha256(token.encode()).hexdigest()
                self.db.revoke_session(token_hash)
                return None
            
            return None
            
        except Exception as e:
            self.logger.error(f"Token validation failed: {e}")
            return None
    
    def get_user(self, user_id: str) -> Optional[Dict[str, Any]]:
        """Get user by ID."""
        return self.db.get_user_by_id(user_id)
    
    def get_user_by_email(self, email: str) -> Optional[Dict[str, Any]]:
        """Get user by email."""
        return self.db.get_user_by_email(email)
    
    def authenticate_user(self, email: str, password: str) -> Optional[Dict[str, Any]]:
        """Authenticate user with email and password."""
        return self.db.authenticate_user(email, password)
    
    def register_user(self, email: str, password: str, name: str) -> Dict[str, Any]:
        """Register a new user."""
        return self.db.create_user(email, password, name)
    
    def check_layer_access(
        self, 
        layer_user_id: str, 
        layer_is_public: bool, 
        requesting_user_id: Optional[str]
    ) -> bool:
        """
        Check if a user can access a layer.
        
        Args:
            layer_user_id: ID of the user who owns the layer
            layer_is_public: Whether the layer is public
            requesting_user_id: ID of the user requesting access
            
        Returns:
            True if access is allowed
        """
        # Debug logging
        self.logger.info(f"Access check: layer_user_id={layer_user_id}, layer_is_public={layer_is_public}, requesting_user_id={requesting_user_id}")
        
        # SECURITY REQUIREMENT: Only authenticated users can see any layers
        # This prevents unauthorized access to the layer system
        if not requesting_user_id:
            self.logger.info(f"Access DENIED: No requesting user ID")
            return False
        
        # Authenticated users can see their own layers (private or public)
        if requesting_user_id == layer_user_id:
            self.logger.info(f"Access GRANTED: User owns layer")
            return True
        
        # Authenticated users can see other users' public layers
        if layer_is_public:
            self.logger.info(f"Access GRANTED: Layer is public and user is authenticated")
            return True
        
        # No access to private layers of other users
        self.logger.info(f"Access DENIED: Private layer of different user")
        return False
    
    def check_layer_modify_access(
        self, 
        layer_user_id: str, 
        requesting_user_id: Optional[str]
    ) -> bool:
        """
        Check if a user can modify (edit, delete, train) a layer.
        Only the owner can modify a layer.
        """
        return requesting_user_id and requesting_user_id == layer_user_id


# Global auth manager instance
auth_manager = AuthManager()


def get_auth_manager() -> AuthManager:
    """Get the global auth manager instance."""
    return auth_manager 