"""
VST/AU Plugin Manager for AbletonMCP_AI

Manages VST and AU plugin detection, loading, and parameter configuration.
Supports popular plugins like Serum, Massive, Sylenth1, FabFilter, and ValhallaDSP.
"""
from __future__ import annotations

import os
import json
import logging
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, asdict
from enum import Enum

logger = logging.getLogger(__name__)


class PluginType(Enum):
    """Types of plugins supported."""
    VST2 = "VST2"
    VST3 = "VST3"
    AU = "AU"  # Audio Units (macOS)


class PluginCategory(Enum):
    """Categories of plugins."""
    SYNTH = "synth"
    EFFECT = "effect"
    EQ = "eq"
    COMPRESSOR = "compressor"
    REVERB = "reverb"
    DELAY = "delay"
    UTILITY = "utility"


@dataclass
class PluginInfo:
    """Information about a detected plugin."""
    name: str
    display_name: str
    plugin_type: PluginType
    category: PluginCategory
    manufacturer: str
    path: Optional[str] = None
    is_installed: bool = False
    version: str = ""
    presets: List[str] = None
    
    def __post_init__(self):
        if self.presets is None:
            self.presets = []


@dataclass
class ParameterInfo:
    """Information about a plugin parameter."""
    name: str
    display_name: str
    min_value: float
    max_value: float
    default_value: float
    value_type: str = "float"  # float, int, bool


# Popular plugin database with known parameters
POPULAR_PLUGINS = {
    # Synths
    "serum": PluginInfo(
        name="Serum",
        display_name="Xfer Serum",
        plugin_type=PluginType.VST2,
        category=PluginCategory.SYNTH,
        manufacturer="Xfer Records",
        is_installed=False,
        presets=["Init", "Bass - Basic", "Lead - Saw", "Pad - Warm"]
    ),
    "massive": PluginInfo(
        name="Massive",
        display_name="Native Instruments Massive",
        plugin_type=PluginType.VST2,
        category=PluginCategory.SYNTH,
        manufacturer="Native Instruments",
        is_installed=False,
        presets=["Init", "Bass - Deep", "Lead - Scream", "Pad - Atmosphere"]
    ),
    "sylenth1": PluginInfo(
        name="Sylenth1",
        display_name="LennarDigital Sylenth1",
        plugin_type=PluginType.VST2,
        category=PluginCategory.SYNTH,
        manufacturer="LennarDigital",
        is_installed=False,
        presets=["Init", "Bass - Sub", "Lead - SuperSaw", "Pad - Cloud"]
    ),
    
    # FabFilter Effects
    "pro-q": PluginInfo(
        name="Pro-Q",
        display_name="FabFilter Pro-Q 3",
        plugin_type=PluginType.VST3,
        category=PluginCategory.EQ,
        manufacturer="FabFilter",
        is_installed=False,
        presets=["Default", "Low Cut", "High Cut", "Vocal", "Drums", "Mastering"]
    ),
    "pro-c": PluginInfo(
        name="Pro-C",
        display_name="FabFilter Pro-C 2",
        plugin_type=PluginType.VST3,
        category=PluginCategory.COMPRESSOR,
        manufacturer="FabFilter",
        is_installed=False,
        presets=["Default", "Vocal", "Drums", "Bus", "Mastering"]
    ),
    "pro-r": PluginInfo(
        name="Pro-R",
        display_name="FabFilter Pro-R",
        plugin_type=PluginType.VST3,
        category=PluginCategory.REVERB,
        manufacturer="FabFilter",
        is_installed=False,
        presets=["Default", "Hall", "Room", "Plate", "Vocal"]
    ),
    
    # ValhallaDSP Effects
    "valhalla_room": PluginInfo(
        name="ValhallaRoom",
        display_name="ValhallaRoom",
        plugin_type=PluginType.VST2,
        category=PluginCategory.REVERB,
        manufacturer="ValhallaDSP",
        is_installed=False,
        presets=["Default", "Small Room", "Medium Room", "Large Hall", "Cathedral"]
    ),
    "valhalla_vintage_verb": PluginInfo(
        name="ValhallaVintageVerb",
        display_name="ValhallaVintageVerb",
        plugin_type=PluginType.VST2,
        category=PluginCategory.REVERB,
        manufacturer="ValhallaDSP",
        is_installed=False,
        presets=["Default", "1970s", "1980s", "1990s", "Modern"]
    ),
    "valhalla_delay": PluginInfo(
        name="ValhallaDelay",
        display_name="ValhallaDelay",
        plugin_type=PluginType.VST2,
        category=PluginCategory.DELAY,
        manufacturer="ValhallaDSP",
        is_installed=False,
        presets=["Default", "Tape", "Ping Pong", "Reverse"]
    ),
    "valhalla_supermassive": PluginInfo(
        name="ValhallaSupermassive",
        display_name="ValhallaSupermassive",
        plugin_type=PluginType.VST2,
        category=PluginCategory.DELAY,
        manufacturer="ValhallaDSP",
        is_installed=False,
        presets=["Default", "Sagittarius", "Great Wall", "Circinus"]
    ),
}

# Known parameter mappings for popular plugins
PLUGIN_PARAMETERS = {
    "serum": {
        "osc_a_wave": ParameterInfo("osc_a_wave", "Osc A Waveform", 0, 100, 0),
        "osc_a_level": ParameterInfo("osc_a_level", "Osc A Level", 0, 1, 0.8),
        "osc_b_wave": ParameterInfo("osc_b_wave", "Osc B Waveform", 0, 100, 0),
        "osc_b_level": ParameterInfo("osc_b_level", "Osc B Level", 0, 1, 0),
        "filter_cutoff": ParameterInfo("filter_cutoff", "Filter Cutoff", 0, 22000, 22000),
        "filter_resonance": ParameterInfo("filter_resonance", "Filter Resonance", 0, 1, 0),
        "attack": ParameterInfo("attack", "Amp Attack", 0, 10, 0.01),
        "decay": ParameterInfo("decay", "Amp Decay", 0, 10, 0),
        "sustain": ParameterInfo("sustain", "Amp Sustain", 0, 1, 1),
        "release": ParameterInfo("release", "Amp Release", 0, 10, 0.5),
    },
    "massive": {
        "osc1_pitch": ParameterInfo("osc1_pitch", "Osc 1 Pitch", -24, 24, 0),
        "osc1_wtpos": ParameterInfo("osc1_wtpos", "Osc 1 Wavetable Pos", 0, 100, 0),
        "osc2_pitch": ParameterInfo("osc2_pitch", "Osc 2 Pitch", -24, 24, 0),
        "filter_cutoff": ParameterInfo("filter_cutoff", "Filter Cutoff", 0, 127, 127),
        "filter_resonance": ParameterInfo("filter_resonance", "Filter Resonance", 0, 127, 0),
        "attack": ParameterInfo("attack", "Amp Attack", 0, 127, 0),
        "decay": ParameterInfo("decay", "Amp Decay", 0, 127, 0),
        "sustain": ParameterInfo("sustain", "Amp Sustain", 0, 127, 127),
        "release": ParameterInfo("release", "Amp Release", 0, 127, 20),
    },
    "sylenth1": {
        "osc_a1_wave": ParameterInfo("osc_a1_wave", "Osc A1 Wave", 0, 4, 0),
        "osc_a1_pitch": ParameterInfo("osc_a1_pitch", "Osc A1 Pitch", -10, 10, 0),
        "osc_a2_wave": ParameterInfo("osc_a2_wave", "Osc A2 Wave", 0, 4, 0),
        "cutoff_a": ParameterInfo("cutoff_a", "Filter A Cutoff", 0, 10, 10),
        "resonance_a": ParameterInfo("resonance_a", "Filter A Resonance", 0, 10, 0),
        "attack": ParameterInfo("attack", "Amp Attack", 0, 10, 0),
        "decay": ParameterInfo("decay", "Amp Decay", 0, 10, 0),
        "sustain": ParameterInfo("sustain", "Amp Sustain", 0, 10, 10),
        "release": ParameterInfo("release", "Amp Release", 0, 10, 0),
    },
    "pro-q": {
        "gain": ParameterInfo("gain", "Output Gain", -36, 36, 0),
        "mix": ParameterInfo("mix", "Mix", 0, 100, 100),
        "band1_gain": ParameterInfo("band1_gain", "Band 1 Gain", -30, 30, 0),
        "band1_freq": ParameterInfo("band1_freq", "Band 1 Freq", 10, 30000, 200),
        "band1_q": ParameterInfo("band1_q", "Band 1 Q", 0.025, 40, 1),
        "band2_gain": ParameterInfo("band2_gain", "Band 2 Gain", -30, 30, 0),
        "band2_freq": ParameterInfo("band2_freq", "Band 2 Freq", 10, 30000, 1000),
    },
    "pro-c": {
        "threshold": ParameterInfo("threshold", "Threshold", -60, 0, 0),
        "ratio": ParameterInfo("ratio", "Ratio", 1, 20, 2),
        "attack": ParameterInfo("attack", "Attack", 0.005, 250, 10),
        "release": ParameterInfo("release", "Release", 1, 2500, 100),
        "makeup": ParameterInfo("makeup", "Makeup Gain", -36, 36, 0),
    },
    "valhalla_room": {
        "mix": ParameterInfo("mix", "Mix", 0, 100, 50),
        "decay": ParameterInfo("decay", "Decay", 0.1, 10, 2),
        "size": ParameterInfo("size", "Size", 0, 1, 0.5),
        "predelay": ParameterInfo("predelay", "PreDelay", 0, 200, 20),
    },
    "valhalla_vintage_verb": {
        "mix": ParameterInfo("mix", "Mix", 0, 100, 50),
        "decay": ParameterInfo("decay", "Decay", 0.1, 10, 2),
        "damping": ParameterInfo("damping", "Damping", 0, 100, 50),
    },
}


class VSTManager:
    """Manager for VST/AU plugins in Ableton Live."""
    
    def __init__(self, song=None, connection=None):
        """
        Initialize VST Manager.
        
        Args:
            song: Ableton Live song object (optional)
            connection: TCP connection to Ableton (optional)
        """
        self.song = song
        self.connection = connection
        self._scanned_plugins: Dict[str, PluginInfo] = {}
        self._plugin_cache_file = self._get_cache_path()
        
        # Initialize with known plugins
        self._initialize_plugin_database()
        
        # Try to load cached scan results
        self._load_cached_plugins()
    
    def _get_cache_path(self) -> str:
        """Get path for plugin cache file."""
        script_dir = os.path.dirname(os.path.abspath(__file__))
        return os.path.join(script_dir, "..", "..", "vst_plugin_cache.json")
    
    def _initialize_plugin_database(self):
        """Initialize the plugin database with known popular plugins."""
        for key, info in POPULAR_PLUGINS.items():
            self._scanned_plugins[key] = PluginInfo(
                name=info.name,
                display_name=info.display_name,
                plugin_type=info.plugin_type,
                category=info.category,
                manufacturer=info.manufacturer,
                is_installed=False,
                version=info.version,
                presets=list(info.presets) if info.presets else []
            )
    
    def _load_cached_plugins(self):
        """Load cached plugin scan results."""
        try:
            if os.path.exists(self._plugin_cache_file):
                with open(self._plugin_cache_file, 'r') as f:
                    cached = json.load(f)
                    for key, data in cached.items():
                        if key in self._scanned_plugins:
                            self._scanned_plugins[key].is_installed = data.get('is_installed', False)
                            self._scanned_plugins[key].path = data.get('path')
                            if 'presets' in data and data['presets']:
                                self._scanned_plugins[key].presets = data['presets']
                logger.info(f"Loaded {len(cached)} plugins from cache")
        except Exception as e:
            logger.warning(f"Could not load plugin cache: {e}")
    
    def _save_cached_plugins(self):
        """Save plugin scan results to cache."""
        try:
            cache_data = {}
            for key, info in self._scanned_plugins.items():
                cache_data[key] = {
                    'name': info.name,
                    'is_installed': info.is_installed,
                    'path': info.path,
                    'presets': info.presets
                }
            with open(self._plugin_cache_file, 'w') as f:
                json.dump(cache_data, f, indent=2)
        except Exception as e:
            logger.warning(f"Could not save plugin cache: {e}")
    
    def scan_vst_plugins(self, force_rescan: bool = False) -> Dict[str, Any]:
        """
        Scan for installed VST/AU plugins.
        
        This attempts to detect which popular plugins are installed
        in the system. In a real implementation, this would query
        the operating system's plugin registry or Ableton's plugin list.
        
        Args:
            force_rescan: Force a fresh scan even if cache exists
            
        Returns:
            Dictionary with scan results and installed plugin list
        """
        installed_count = 0
        
        if force_rescan or not any(p.is_installed for p in self._scanned_plugins.values()):
            # In a real implementation, this would:
            # 1. Query Windows Registry for VST2 paths
            # 2. Scan VST3 standard paths
            # 3. Query macOS AudioUnit registry
            # 4. Or query Ableton Live's plugin database
            
            # For now, we simulate detection based on common installation paths
            # and let the user confirm installation when loading
            
            common_paths = self._get_common_plugin_paths()
            
            for key, info in self._scanned_plugins.items():
                # Check if plugin might be installed
                # This is a heuristic - actual detection requires OS-specific calls
                found_path = self._find_plugin_file(info.name, common_paths, info.plugin_type)
                if found_path:
                    info.is_installed = True
                    info.path = found_path
                    installed_count += 1
                    logger.info(f"Detected plugin: {info.name} at {found_path}")
            
            self._save_cached_plugins()
        
        # Build results
        installed = [p.name for p in self._scanned_plugins.values() if p.is_installed]
        not_installed = [p.name for p in self._scanned_plugins.values() if not p.is_installed]
        
        return {
            "total_known": len(self._scanned_plugins),
            "installed_count": len(installed),
            "installed_plugins": installed,
            "not_installed": not_installed,
            "categories": self._get_plugins_by_category(),
            "scan_paths_checked": self._get_common_plugin_paths() if force_rescan else [],
            "cache_file": self._plugin_cache_file,
            "note": "Plugin detection is heuristic. Actual availability verified when loading."
        }
    
    def _get_common_plugin_paths(self) -> List[str]:
        """Get common plugin installation paths."""
        paths = []
        
        # Windows VST2 paths
        if os.name == 'nt':
            paths.extend([
                os.path.expandvars(r"%PROGRAMFILES%\VstPlugins"),
                os.path.expandvars(r"%PROGRAMFILES(x86)%\VstPlugins"),
                os.path.expandvars(r"%COMMONPROGRAMFILES%\VST2"),
                os.path.expandvars(r"%COMMONPROGRAMFILES(x86)%\VST2"),
                os.path.expandvars(r"%PROGRAMFILES%\Common Files\VST2"),
                os.path.expandvars(r"%PROGRAMFILES%\Common Files\VST3"),
                os.path.expandvars(r"%LOCALAPPDATA%\Programs\Common\VST3"),
            ])
        
        # macOS paths (for completeness)
        else:
            paths.extend([
                "/Library/Audio/Plug-Ins/VST",
                "/Library/Audio/Plug-Ins/VST3",
                "/Library/Audio/Plug-Ins/Components",
                os.path.expanduser("~/Library/Audio/Plug-Ins/VST"),
                os.path.expanduser("~/Library/Audio/Plug-Ins/VST3"),
                os.path.expanduser("~/Library/Audio/Plug-Ins/Components"),
            ])
        
        return paths
    
    def _find_plugin_file(self, plugin_name: str, paths: List[str], plugin_type: PluginType) -> Optional[str]:
        """Search for plugin file in given paths."""
        # Normalize plugin name for file search
        name_variants = [
            plugin_name,
            plugin_name.replace(" ", ""),
            plugin_name.replace(" ", "-"),
        ]
        
        # Extension based on plugin type
        extensions = []
        if plugin_type == PluginType.VST2:
            extensions = ['.dll'] if os.name == 'nt' else ['.vst', '.so']
        elif plugin_type == PluginType.VST3:
            extensions = ['.vst3']
        elif plugin_type == PluginType.AU:
            extensions = ['.component']
        
        for path in paths:
            if not os.path.exists(path):
                continue
            
            try:
                for root, dirs, files in os.walk(path):
                    for ext in extensions:
                        for name_variant in name_variants:
                            filename = name_variant + ext
                            if filename.lower() in [f.lower() for f in files]:
                                return os.path.join(root, filename)
                            
                            # Check directories (for bundles)
                            if ext == '.component' or ext == '.vst3':
                                bundle_name = name_variant + ext
                                if bundle_name.lower() in [d.lower() for d in dirs]:
                                    return os.path.join(root, bundle_name)
            except Exception as e:
                logger.debug(f"Error scanning {path}: {e}")
        
        return None
    
    def get_vst_presets(self, plugin_name: str) -> Dict[str, Any]:
        """
        Get available presets for a plugin.
        
        Args:
            plugin_name: Name of the plugin
            
        Returns:
            Dictionary with preset list and plugin info
        """
        key = plugin_name.lower().replace(" ", "_")
        
        # Handle aliases
        aliases = {
            "serum": "serum",
            "xfer_serum": "serum",
            "massive": "massive",
            "ni_massive": "massive",
            "sylenth1": "sylenth1",
            "sylenth": "sylenth1",
            "pro-q": "pro-q",
            "pro_q": "pro-q",
            "fabfilter_pro_q": "pro-q",
            "pro-c": "pro-c",
            "pro_c": "pro-c",
            "fabfilter_pro_c": "pro-c",
            "valhallaroom": "valhalla_room",
            "valhalla_room": "valhalla_room",
            "valhallavintageverb": "valhalla_vintage_verb",
            "valhalla_vintage_verb": "valhalla_vintage_verb",
        }
        
        key = aliases.get(key, key)
        
        if key not in self._scanned_plugins:
            return {
                "status": "error",
                "message": f"Unknown plugin: {plugin_name}",
                "available_plugins": list(self._scanned_plugins.keys())
            }
        
        info = self._scanned_plugins[key]
        
        # Get parameters for this plugin
        params = PLUGIN_PARAMETERS.get(key, {})
        param_list = [asdict(p) for p in params.values()]
        
        return {
            "status": "success",
            "plugin_name": info.name,
            "display_name": info.display_name,
            "manufacturer": info.manufacturer,
            "category": info.category.value,
            "is_installed": info.is_installed,
            "presets": info.presets,
            "parameters": param_list,
            "plugin_type": info.plugin_type.value,
        }
    
    def get_all_plugins(self) -> Dict[str, Any]:
        """Get list of all known plugins with their status."""
        plugins = []
        for key, info in self._scanned_plugins.items():
            plugins.append({
                "key": key,
                "name": info.name,
                "display_name": info.display_name,
                "manufacturer": info.manufacturer,
                "category": info.category.value,
                "is_installed": info.is_installed,
                "plugin_type": info.plugin_type.value,
            })
        
        return {
            "total": len(plugins),
            "installed": sum(1 for p in plugins if p["is_installed"]),
            "plugins": plugins
        }
    
    def _get_plugins_by_category(self) -> Dict[str, List[str]]:
        """Group plugins by category."""
        by_category = {}
        for info in self._scanned_plugins.values():
            cat = info.category.value
            if cat not in by_category:
                by_category[cat] = []
            by_category[cat].append(info.name)
        return by_category
    
    def validate_plugin_installation(self, plugin_name: str) -> Tuple[bool, str]:
        """
        Validate if a plugin is actually installed and usable.
        
        Args:
            plugin_name: Name of the plugin to validate
            
        Returns:
            Tuple of (is_installed, message)
        """
        key = plugin_name.lower().replace(" ", "_")
        
        # Handle common aliases
        aliases = {
            "serum": "serum",
            "xfer_serum": "serum",
            "massive": "massive",
            "ni_massive": "massive",
            "sylenth1": "sylenth1",
            "sylenth": "sylenth1",
            "pro-q": "pro-q",
            "pro-q_3": "pro-q",
            "pro-c": "pro-c",
            "pro-c_2": "pro-c",
            "valhallaroom": "valhalla_room",
            "valhalla_vintage_verb": "valhalla_vintage_verb",
        }
        key = aliases.get(key, key)
        
        if key not in self._scanned_plugins:
            return False, f"Plugin '{plugin_name}' not in database"
        
        info = self._scanned_plugins[key]
        
        if info.is_installed and info.path:
            # Verify file still exists
            if os.path.exists(info.path):
                return True, f"Plugin found at {info.path}"
            else:
                # File moved or deleted, update status
                info.is_installed = False
                info.path = None
                self._save_cached_plugins()
                return False, "Plugin was moved or deleted"
        
        # Not marked as installed, try to find it
        paths = self._get_common_plugin_paths()
        found = self._find_plugin_file(info.name, paths, info.plugin_type)
        
        if found:
            info.is_installed = True
            info.path = found
            self._save_cached_plugins()
            return True, f"Plugin found at {found}"
        
        return False, f"Plugin '{plugin_name}' not found in standard plugin directories"


# Global instance
_vst_manager: Optional[VSTManager] = None


def get_vst_manager(song=None, connection=None) -> VSTManager:
    """Get or create global VST manager instance."""
    global _vst_manager
    if _vst_manager is None:
        _vst_manager = VSTManager(song=song, connection=connection)
    return _vst_manager


def scan_vst_plugins(force_rescan: bool = False) -> Dict[str, Any]:
    """Scan for installed VST/AU plugins."""
    manager = get_vst_manager()
    return manager.scan_vst_plugins(force_rescan=force_rescan)


def get_vst_presets(plugin_name: str) -> Dict[str, Any]:
    """Get presets for a plugin."""
    manager = get_vst_manager()
    return manager.get_vst_presets(plugin_name)


def get_all_plugins() -> Dict[str, Any]:
    """Get all known plugins."""
    manager = get_vst_manager()
    return manager.get_all_plugins()


def validate_plugin(plugin_name: str) -> Tuple[bool, str]:
    """Validate plugin installation."""
    manager = get_vst_manager()
    return manager.validate_plugin_installation(plugin_name)


def get_plugin_parameters(plugin_name: str) -> Dict[str, ParameterInfo]:
    """Get parameters for a plugin."""
    key = plugin_name.lower().replace(" ", "_")
    return PLUGIN_PARAMETERS.get(key, {})
