"""
Share Token Management for NoteDiscovery
Handles creating, storing, and revoking share tokens for public note access.
"""

import json
import secrets
import string
from pathlib import Path
from datetime import datetime, timezone
from typing import Optional, Dict, Any
import threading

# Thread lock for safe concurrent access
_lock = threading.Lock()


def generate_token(length: int = 16) -> str:
    """Generate a URL-safe random token."""
    # Use alphanumeric + underscore/hyphen (URL-safe)
    alphabet = string.ascii_letters + string.digits + '_-'
    return ''.join(secrets.choice(alphabet) for _ in range(length))


def get_tokens_file_path(data_dir: str) -> Path:
    """Get the path to the share tokens file."""
    return Path(data_dir) / '.share-tokens.json'


def load_tokens(data_dir: str) -> Dict[str, Dict[str, Any]]:
    """Load share tokens from file."""
    tokens_file = get_tokens_file_path(data_dir)
    
    if not tokens_file.exists():
        return {}
    
    try:
        with open(tokens_file, 'r', encoding='utf-8') as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return {}


def save_tokens(data_dir: str, tokens: Dict[str, Dict[str, Any]]) -> bool:
    """Save share tokens to file."""
    tokens_file = get_tokens_file_path(data_dir)
    
    try:
        # Ensure parent directory exists
        tokens_file.parent.mkdir(parents=True, exist_ok=True)
        
        with open(tokens_file, 'w', encoding='utf-8') as f:
            json.dump(tokens, f, indent=2, ensure_ascii=False)
        return True
    except IOError as e:
        print(f"Failed to save share tokens: {e}")
        return False


def create_share_token(data_dir: str, note_path: str, theme: str = "light") -> Optional[str]:
    """
    Create a share token for a note.
    If the note already has a token, returns the existing one.
    
    Args:
        data_dir: Path to the data directory
        note_path: Path to the note (relative to notes_dir)
        theme: The theme to use when viewing the shared note
    
    Returns:
        The share token, or None on error
    """
    with _lock:
        tokens = load_tokens(data_dir)
        
        # Check if note already has a token
        for token, info in tokens.items():
            if info.get('path') == note_path:
                return token
        
        # Generate new token
        token = generate_token()
        
        # Ensure uniqueness (extremely unlikely collision, but check anyway)
        while token in tokens:
            token = generate_token()
        
        # Store token with theme
        tokens[token] = {
            'path': note_path,
            'theme': theme,
            'created': datetime.now(timezone.utc).isoformat()
        }
        
        if save_tokens(data_dir, tokens):
            return token
        return None


def get_share_token(data_dir: str, note_path: str) -> Optional[str]:
    """
    Get the share token for a note, if it exists.
    
    Args:
        data_dir: Path to the data directory
        note_path: Path to the note
    
    Returns:
        The share token, or None if not shared
    """
    tokens = load_tokens(data_dir)
    
    for token, info in tokens.items():
        if info.get('path') == note_path:
            return token
    
    return None


def get_note_by_token(data_dir: str, token: str) -> Optional[Dict[str, Any]]:
    """
    Get the note info for a share token.
    
    Args:
        data_dir: Path to the data directory
        token: The share token
    
    Returns:
        Dict with 'path' and 'theme', or None if token not found
    """
    tokens = load_tokens(data_dir)
    
    if token in tokens:
        return {
            'path': tokens[token].get('path'),
            'theme': tokens[token].get('theme', 'light')
        }
    
    return None


def get_all_shared_paths(data_dir: str) -> list:
    """
    Get a list of all currently shared note paths.
    Used for displaying share indicators in the UI.
    
    Args:
        data_dir: Path to the data directory
    
    Returns:
        List of note paths that are currently shared
    """
    tokens = load_tokens(data_dir)
    return [info.get('path') for info in tokens.values() if info.get('path')]


def revoke_share_token(data_dir: str, note_path: str) -> bool:
    """
    Revoke (delete) the share token for a note.
    
    Args:
        data_dir: Path to the data directory
        note_path: Path to the note
    
    Returns:
        True if token was revoked, False if not found or error
    """
    with _lock:
        tokens = load_tokens(data_dir)
        
        # Find and remove token for this note
        token_to_remove = None
        for token, info in tokens.items():
            if info.get('path') == note_path:
                token_to_remove = token
                break
        
        if token_to_remove:
            del tokens[token_to_remove]
            return save_tokens(data_dir, tokens)
        
        return False


def get_share_info(data_dir: str, note_path: str) -> Optional[Dict[str, Any]]:
    """
    Get share information for a note.
    
    Args:
        data_dir: Path to the data directory
        note_path: Path to the note
    
    Returns:
        Dict with token, theme, and created date, or None if not shared
    """
    tokens = load_tokens(data_dir)
    
    for token, info in tokens.items():
        if info.get('path') == note_path:
            return {
                'token': token,
                'theme': info.get('theme', 'light'),
                'created': info.get('created'),
                'shared': True
            }
    
    return {'shared': False}


def update_token_path(data_dir: str, old_path: str, new_path: str) -> bool:
    """
    Update the path for a token when a note is moved/renamed.
    
    Args:
        data_dir: Path to the data directory
        old_path: Old note path
        new_path: New note path
    
    Returns:
        True if updated, False if not found or error
    """
    with _lock:
        tokens = load_tokens(data_dir)
        
        for token, info in tokens.items():
            if info.get('path') == old_path:
                info['path'] = new_path
                return save_tokens(data_dir, tokens)
        
        return False


def delete_token_for_note(data_dir: str, note_path: str) -> bool:
    """
    Delete the share token when a note is deleted.
    Alias for revoke_share_token for clarity.
    """
    return revoke_share_token(data_dir, note_path)
