import obspython as obs
import os
import subprocess
import json
import platform
from datetime import datetime
import time

# Try to import mutagen at module level
try:
    from mutagen import File as MutagenFile
    HAS_MUTAGEN = True
except ImportError:
    HAS_MUTAGEN = False
    MutagenFile = None

# ==================== Version Info ====================
__version__ = "1.3.0"
__author__ = "icy404"
__updated__ = "2025-11-11"

# ==================== Configuration ====================
music_folder = ""
text_source_name = ""
vlc_source_name = ""
output_file = ""
default_text = "your_brand_name"
update_interval = 2000  # milliseconds
show_format = "{title} - {artist}"  # Customizable format
enabled = True
use_vlc_source = True

# ==================== State ====================
metadata_cache = {}
last_file = ""
last_song = ""
stats = {"total_plays": 0, "session_start": None}

# ==================== Cached OBS Process (Windows) ====================
_obs_cache = {"pid": None, "time": 0}
OBS_CACHE_DURATION = 2  # seconds

def get_obs_process_cached():
    """Return the OBS process (Windows) with caching to reduce CPU load"""
    try:
        import psutil
    except ImportError:
        script_log("psutil not installed - cannot use folder monitoring on Windows", "ERROR")
        return None
    
    now = time.time()
    if _obs_cache["pid"] and now - _obs_cache["time"] < OBS_CACHE_DURATION:
        try:
            return psutil.Process(_obs_cache["pid"])
        except psutil.NoSuchProcess:
            _obs_cache["pid"] = None  # process died
    
    # Search for OBS process
    for proc in psutil.process_iter(["name", "pid"]):
        if "obs" in proc.info["name"].lower():
            _obs_cache["pid"] = proc.info["pid"]
            _obs_cache["time"] = now
            return proc
    
    _obs_cache["pid"] = None
    return None

# ==================== Script Info / Properties ====================
def script_description():
    return f"""<center><h2>🎵 VLC Now Playing Monitor</h2></center>
    
    <p>Automatically detects and displays currently playing songs from VLC Video Source.</p>
    
    <p><b>Quick Setup:</b></p>
    <ol>
        <li>Create a <b>Text (GDI+)</b> or <b>Text (FreeType 2)</b> source</li>
        <li>Select it in 'Text Source' dropdown below</li>
        <li>Select your VLC source OR set music folder path</li>
        <li>Customize the display format</li>
        <li>Done! It updates automatically</li>
    </ol>
    
    <p><b>Format Variables:</b></p>
    <ul>
        <li><code>{{title}}</code> - Song title</li>
        <li><code>{{artist}}</code> - Artist name</li>
        <li><code>{{album}}</code> - Album name</li>
        <li><code>{{filename}}</code> - File name</li>
    </ul>
    
    <p><i>Example: "{{title}} - {{artist}}" displays "Bohemian Rhapsody - Queen"</i></p>
    
    <hr>
    <p><small>v{__version__} | Updated: {__updated__}</small></p>
    <p><small>Compatible with OBS 27+ | Cross-platform (Windows/Linux/macOS)</small></p>
    """

# ==================== Script Defaults ====================
def script_defaults(settings):
    obs.obs_data_set_default_string(settings, "default_text", "BeatBoy")
    obs.obs_data_set_default_int(settings, "update_interval", 2)
    obs.obs_data_set_default_string(settings, "show_format", "{title} - {artist}")
    obs.obs_data_set_default_string(settings, "music_folder", "")
    obs.obs_data_set_default_string(settings, "vlc_source", "")
    obs.obs_data_set_default_string(settings, "output_file", "/home/turka/radio/now_playing.txt")
    obs.obs_data_set_default_bool(settings, "enabled", True)

# ==================== Script Update ====================
def script_update(settings):
    global music_folder, output_file, default_text, text_source_name
    global update_interval, show_format, enabled, vlc_source_name
    
    music_folder = obs.obs_data_get_string(settings, "music_folder")
    vlc_source_name = obs.obs_data_get_string(settings, "vlc_source")
    output_file = obs.obs_data_get_string(settings, "output_file")
    
    if output_file and '~' in output_file:
        output_file = os.path.expanduser(output_file)
    if music_folder and '~' in music_folder:
        music_folder = os.path.expanduser(music_folder)
    
    default_text = obs.obs_data_get_string(settings, "default_text")
    text_source_name = obs.obs_data_get_string(settings, "text_source")
    show_format = obs.obs_data_get_string(settings, "show_format")
    enabled = obs.obs_data_get_bool(settings, "enabled")
    update_interval = obs.obs_data_get_int(settings, "update_interval") * 1000
    
    obs.timer_remove(update_song)
    if enabled and (vlc_source_name or music_folder):
        obs.timer_add(update_song, update_interval)
        update_song()
        method = "VLC source" if vlc_source_name else "folder monitoring"
        script_log(f"Monitoring started using {method} (text source: {'set' if text_source_name else 'not set'})")
    else:
        missing = []
        if not enabled:
            missing.append("disabled")
        if not vlc_source_name and not music_folder:
            missing.append("no VLC source or music folder")
        script_log(f"Monitoring stopped ({', '.join(missing)})")

def script_load(settings):
    global stats
    stats["session_start"] = datetime.now()
    
    if not HAS_MUTAGEN:
        script_log("⚠️ Mutagen not installed - metadata will be limited to filenames", "WARN")
        script_log("   Install with: pip install mutagen", "INFO")
    
    script_log(f"✓ Script loaded successfully on {platform.system()}")

def script_unload():
    obs.timer_remove(update_song)
    script_log(f"Script unloaded. Total plays this session: {stats['total_plays']}")

# ==================== Logging / Status ====================
def script_log(message, level="INFO"):
    print(f"[VLC Monitor][{level}] {message}")

# ==================== VLC / File Detection ====================
def get_vlc_current_file():
    if not vlc_source_name:
        return None
    
    try:
        source = obs.obs_get_source_by_name(vlc_source_name)
        if not source:
            return None
        
        settings = obs.obs_source_get_settings(source)
        playlist_array = obs.obs_data_get_array(settings, "playlist")
        if not playlist_array:
            obs.obs_data_release(settings)
            obs.obs_source_release(source)
            return None
        
        count = obs.obs_data_array_count(playlist_array)
        current_file = None
        for i in range(count):
            item = obs.obs_data_array_item(playlist_array, i)
            if item:
                filepath = obs.obs_data_get_string(item, "value")
                if filepath and os.path.exists(filepath):
                    ext = os.path.splitext(filepath)[1].lower()
                    if ext in ['.mp3', '.flac', '.ogg', '.m4a', '.wav', '.opus', '.aac', '.wma']:
                        current_file = filepath
                        obs.obs_data_release(item)
                        break
                obs.obs_data_release(item)
        
        obs.obs_data_array_release(playlist_array)
        obs.obs_data_release(settings)
        obs.obs_source_release(source)
        return current_file
        
    except Exception as e:
        script_log(f"Error reading VLC source: {e}", "ERROR")
        return None

def get_playing_file_windows():
    """Find which music file is currently being read (Windows)"""
    if not music_folder or not os.path.exists(music_folder):
        return None
    
    obs_process = get_obs_process_cached()
    if not obs_process:
        return None
    
    extensions = ('.mp3', '.flac', '.ogg', '.m4a', '.wav', '.opus', '.aac', '.wma')
    
    try:
        for file in obs_process.open_files():
            if music_folder in file.path and file.path.lower().endswith(extensions):
                if os.path.exists(file.path):
                    return file.path
    except (psutil.AccessDenied, psutil.NoSuchProcess):
        script_log("Cannot access OBS process files (try running OBS as administrator)", "WARN")
        return None
    except Exception as e:
        script_log(f"Error reading OBS process files: {e}", "ERROR")
        return None
    
    return None

def get_playing_file_lsof():
    if not music_folder or not os.path.exists(music_folder):
        return None
    try:
        result = subprocess.run(
            ['lsof', '-c', 'obs'],
            capture_output=True,
            text=True,
            timeout=2
        )
        extensions = ('.mp3', '.flac', '.ogg', '.m4a', '.wav', '.opus', '.aac', '.wma')
        for line in result.stdout.split('\n'):
            if music_folder in line and any(ext in line.lower() for ext in extensions):
                parts = line.split()
                if len(parts) > 8:
                    filepath = ' '.join(parts[8:])
                    if os.path.exists(filepath) and os.path.isfile(filepath):
                        return filepath
        return None
    except Exception as e:
        script_log(f"Error getting file via lsof: {e}", "ERROR")
        return None

def get_playing_file():
    system = platform.system()
    if system == "Windows":
        if music_folder:
            return get_playing_file_windows()
        return None
    if vlc_source_name:
        file = get_vlc_current_file()
        if file:
            return file
    if music_folder and system in ["Linux", "Darwin"]:
        return get_playing_file_lsof()
    script_log(f"Unsupported platform: {system}", "ERROR")
    return None

# ==================== Metadata / Display ====================
def get_song_metadata(filepath):
    global metadata_cache
    try:
        mtime = os.path.getmtime(filepath)
        cache_key = f"{filepath}:{mtime}"
        if cache_key in metadata_cache:
            return metadata_cache[cache_key]
    except OSError:
        return None
    
    filename_no_ext = os.path.splitext(os.path.basename(filepath))[0]
    clean_title = filename_no_ext.replace('_', ' ')
    
    metadata = {'title': clean_title, 'artist': '', 'album': '', 'filename': os.path.basename(filepath)}
    
    if music_folder and music_folder in filepath:
        metadata_cache[cache_key] = metadata
        return metadata
    
    if HAS_MUTAGEN and MutagenFile is not None:
        try:
            audio = MutagenFile(filepath)
            if audio:
                for key in ['title', 'artist', 'album']:
                    if key in audio:
                        val = audio[key]
                        metadata[key] = str(val[0]) if isinstance(val, list) else str(val)
                metadata = {k: v.strip() if v else '' for k,v in metadata.items()}
        except Exception as e:
            script_log(f"Error reading metadata: {e}", "WARN")
    
    metadata_cache[cache_key] = metadata
    if len(metadata_cache) > 500:
        keys = list(metadata_cache.keys())
        for key in keys[:250]:
            del metadata_cache[key]
    return metadata

def format_song_display(metadata):
    if not metadata:
        return default_text
    title = metadata.get('title')
    artist = metadata.get('artist')
    filename = metadata.get('filename', '')
    if title and artist:
        return f"{title} - {artist}"
    elif title:
        return title
    else:
        name = os.path.splitext(filename)[0].replace('_', ' ').strip()
        return name if name else default_text

def update_text_source(text):
    if not text_source_name:
        return False
    source = obs.obs_get_source_by_name(text_source_name)
    if not source:
        script_log(f"Text source '{text_source_name}' not found", "WARN")
        return False
    try:
        settings = obs.obs_data_create()
        obs.obs_data_set_string(settings, "text", text)
        obs.obs_source_update(source, settings)
        obs.obs_data_release(settings)
        obs.obs_source_release(source)
        return True
    except Exception as e:
        script_log(f"Error updating text source: {e}", "ERROR")
        obs.obs_source_release(source)
        return False

def write_to_file(text):
    if not output_file or not output_file.strip():
        return
    try:
        expanded_path = os.path.expanduser(output_file)
        output_dir = os.path.dirname(expanded_path)
        if output_dir and not os.path.exists(output_dir):
            os.makedirs(output_dir, exist_ok=True)
        with open(expanded_path, 'w', encoding='utf-8') as f:
            f.write(f"{text}")
        script_log(f"Wrote to file: {text}")
    except Exception as e:
        script_log(f"Error writing to file: {e}", "ERROR")

# ==================== Main Update Loop ====================
def update_song():
    global last_file, last_song, stats
    if not enabled:
        return
    current_file = get_playing_file()
    if current_file and current_file != last_file:
        metadata = get_song_metadata(current_file)
        if not metadata:
            return
        current_song = format_song_display(metadata)
        if current_song != last_song:
            update_text_source(current_song)
            write_to_file(current_song)
            script_log(f"♫ Now Playing: {current_song}")
            stats["total_plays"] += 1
            last_song = current_song
        last_file = current_file
    elif not current_file and last_file:
        update_text_source(default_text)
        write_to_file(default_text)
        last_file = ""
        last_song = ""

