"""
AbletonMCP AI Server v2 - Servidor MCP robusto para generación musical
Integra FastMCP con Ableton Live 12 via socket TCP y Max for Live via UDP

Para ejecutar:
    python -m AbletonMCP_AI.MCP_Server.server_v2

O con uv:
    uv run python -m AbletonMCP_AI.MCP_Server.server_v2
"""

from mcp.server.fastmcp import FastMCP, Context
import socket
import json
import logging
import sys
from dataclasses import dataclass
from contextlib import asynccontextmanager
from typing import AsyncIterator, Dict, Any, List, Optional
from pathlib import Path
from datetime import datetime

# Añadir el path para imports
sys.path.insert(0, str(Path(__file__).parent.parent))

try:
#     from song_generator import SongGenerator, StyleConfig
    from sample_index import SampleIndex
except ImportError as e:
    print(f"Error importando módulos locales: {e}")
    SongGenerator = None
    SampleIndex = None

# Configuración de logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler(Path(__file__).parent / 'server_v2.log', mode='a')
    ]
)
logger = logging.getLogger("AbletonMCP-AI-v2")

# ============================================================================
# CONSTANTES Y CONFIGURACIÓN
# ============================================================================

DEFAULT_ABLETON_PORT = 9877
DEFAULT_MAX_PORT = 9879
MAX_HOST = "127.0.0.1"
ABLETON_HOST = "localhost"
SAMPLES_DIR = r"C:\Users\ren\embeddings\all_tracks"

# Colores por tipo de track
TRACK_COLORS = {
    'kick': 10,      # Rojo
    'snare': 20,     # Verde
    'hat': 5,        # Amarillo
    'clap': 45,      # Naranja
    'bass': 30,      # Azul
    'synth': 50,     # Rosa/Magenta
    'chords': 60,    # Púrpura
    'fx': 25,        # Verde claro
    'vocal': 15,     # Naranja oscuro
}

# Instrucciones para el productor (contexto de IA)
PRODUCER_INSTRUCTIONS = """
Eres AbletonMCP-AI v2, un productor musical experto integrado con Ableton Live 12 y Max for Live.
Tu objetivo es crear música electrónica profesional mediante prompts en lenguaje natural.

CAPACIDADES PRINCIPALES:
1. Generar tracks completos con estructura profesional (Intro, Build, Drop, Break, Outro)
2. Crear patrones MIDI para diferentes géneros (Techno, House, Trance, Tech-House, etc.)
3. Seleccionar y cargar samples apropiados desde la librería local
4. Enviar rutas de samples a Max for Live para carga dinámica
5. Configurar BPM, tonalidad y estructura musical
6. Controlar transporte (play, stop, tempo)
7. Crear clips y escenas en Ableton

HERRAMIENTAS DISPONIBLES:
- generate_song(genre, style, bpm): Genera una canción completa
- load_sample_kit(genre): Carga un kit de samples para un género
- create_pattern(instrument, pattern_type): Crea patrones MIDI
- control_transport(action): Controla reproducción
- get_session_info(): Obtiene información de la sesión

ESTILOS SOPORTADOS:
- Techno: Industrial, Peak Time, Dub, Minimal, Acid
- House: Deep, Tech-House, Progressive, Afro, Classic 90s
- Trance: Psy, Progressive, Uplifting
- Drum & Bass: Liquid, Neuro, Jump-up, Jungle

FLUJO DE TRABAJO:
1. Analizar el prompt del usuario para extraer género, BPM, tonalidad, mood
2. Detectar samples disponibles en la librería
3. Generar patrones MIDI característicos del género
4. Enviar comandos a Ableton via socket TCP
5. Enviar rutas de samples a Max via UDP
6. Proporcionar feedback sobre lo creado

REGLAS:
- Siempre verifica la conexión con Ableton antes de ejecutar comandos
- Usa valores por defecto razonables si el usuario no especifica
- Organiza los tracks con colores consistentes
- Maneja errores gracefully y proporciona mensajes útiles
- Loggea todas las operaciones para debugging
""".strip()


# ============================================================================
# CLASES DE CONEXIÓN
# ============================================================================

@dataclass
class AbletonConnection:
    """Gestiona la conexión TCP con Ableton Live"""
    host: str = ABLETON_HOST
    port: int = DEFAULT_ABLETON_PORT
    sock: Optional[socket.socket] = None
    connected: bool = False
    last_error: Optional[str] = None

    def connect(self, timeout: float = 5.0) -> bool:
        """Conecta al Remote Script de Ableton"""
        if self.connected and self.sock:
            return True

        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.settimeout(timeout)
            self.sock.connect((self.host, self.port))
            self.sock.settimeout(None)  # Non-blocking después de conectar
            self.connected = True
            self.last_error = None
            logger.info(f"Conectado a Ableton en {self.host}:{self.port}")
            return True
        except socket.timeout:
            self.last_error = f"Timeout conectando a {self.host}:{self.port}"
            logger.error(self.last_error)
            self.sock = None
            self.connected = False
            return False
        except Exception as e:
            self.last_error = f"Error conectando a Ableton: {e}"
            logger.error(self.last_error)
            self.sock = None
            self.connected = False
            return False

    def disconnect(self):
        """Desconecta de Ableton"""
        if self.sock:
            try:
                self.sock.close()
            except Exception as e:
                logger.error(f"Error desconectando: {e}")
            finally:
                self.sock = None
                self.connected = False
                logger.info("Desconectado de Ableton")

    def send_command(self, command_type: str, params: Dict[str, Any] = None,
                     timeout: float = 15.0) -> Dict[str, Any]:
        """Envía un comando a Ableton y retorna la respuesta"""
        if not self.connected and not self.connect():
            return {"status": "error", "message": "No conectado a Ableton"}

        command = {
            "type": command_type,
            "params": params or {}
        }

        try:
            logger.debug(f"Enviando comando: {command_type}")
            self.sock.sendall(json.dumps(command).encode('utf-8'))

            # Recibir respuesta
            self.sock.settimeout(timeout)
            chunks = []

            while True:
                try:
                    chunk = self.sock.recv(8192)
                    if not chunk:
                        break
                    chunks.append(chunk)

                    # Intentar parsear JSON completo
                    try:
                        data = b''.join(chunks)
                        response = json.loads(data.decode('utf-8'))
                        return response
                    except json.JSONDecodeError:
                        continue

                except socket.timeout:
                    logger.warning("Timeout esperando respuesta")
                    break

            # Respuesta incompleta
            if chunks:
                data = b''.join(chunks)
                try:
                    return json.loads(data.decode('utf-8'))
                except Exception:
                    return {"status": "error", "message": "Respuesta JSON incompleta"}
            else:
                return {"status": "error", "message": "No se recibió respuesta"}

        except socket.error as e:
            self.connected = False
            self.last_error = f"Error de socket: {e}"
            logger.error(self.last_error)
            return {"status": "error", "message": str(e)}
        except Exception as e:
            self.connected = False
            self.last_error = f"Error en comunicación: {e}"
            logger.error(self.last_error)
            return {"status": "error", "message": str(e)}


@dataclass
class MaxConnection:
    """Gestiona la conexión UDP con Max for Live"""
    host: str = MAX_HOST
    port: int = DEFAULT_MAX_PORT
    sock: Optional[socket.socket] = None

    def __post_init__(self):
        self._init_socket()

    def _init_socket(self):
        """Inicializa el socket UDP"""
        try:
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            logger.info(f"Socket UDP inicializado para Max en {self.host}:{self.port}")
        except Exception as e:
            logger.error(f"Error inicializando socket UDP: {e}")
            self.sock = None

    def send_message(self, message: Dict[str, Any]) -> bool:
        """Envía un mensaje JSON a Max for Live via UDP"""
        if not self.sock:
            self._init_socket()
            if not self.sock:
                return False

        try:
            data = json.dumps(message).encode('utf-8')
            self.sock.sendto(data, (self.host, self.port))
            logger.debug(f"Mensaje enviado a Max: {message.get('type', 'unknown')}")
            return True
        except Exception as e:
            logger.error(f"Error enviando mensaje a Max: {e}")
            return False

    def send_sample_path(self, track_index: int, sample_path: str,
                         slot: int = 0) -> bool:
        """Envía una ruta de sample a Max para cargar"""
        message = {
            "type": "load_sample",
            "track_index": track_index,
            "sample_path": sample_path,
            "slot": slot,
            "timestamp": datetime.now().isoformat()
        }
        return self.send_message(message)

    def send_sample_kit(self, kit: Dict[str, List[Dict]]) -> bool:
        """Envía un kit completo de samples a Max"""
        message = {
            "type": "load_sample_kit",
            "kit": kit,
            "timestamp": datetime.now().isoformat()
        }
        return self.send_message(message)

    def send_command(self, command: str, params: Dict[str, Any] = None) -> bool:
        """Envía un comando genérico a Max"""
        message = {
            "type": "command",
            "command": command,
            "params": params or {},
            "timestamp": datetime.now().isoformat()
        }
        return self.send_message(message)


# ============================================================================
# GESTORES GLOBALES
# ============================================================================

_ableton_connection: Optional[AbletonConnection] = None
_max_connection: Optional[MaxConnection] = None
_sample_index: Optional['SampleIndex'] = None
_song_generator: Optional['SongGenerator'] = None


def get_ableton_connection() -> AbletonConnection:
    """Obtiene o crea la conexión con Ableton"""
    global _ableton_connection
    if _ableton_connection is None:
        _ableton_connection = AbletonConnection()
    return _ableton_connection


def get_max_connection() -> MaxConnection:
    """Obtiene o crea la conexión con Max"""
    global _max_connection
    if _max_connection is None:
        _max_connection = MaxConnection()
    return _max_connection


def get_sample_index() -> Optional['SampleIndex']:
    """Obtiene o crea el índice de samples"""
    global _sample_index
    if _sample_index is None and SampleIndex is not None:
        try:
            _sample_index = SampleIndex(SAMPLES_DIR)
        except Exception as e:
            logger.error(f"Error cargando índice de samples: {e}")
    return _sample_index


def get_song_generator() -> Optional['SongGenerator']:
    """Obtiene o crea el generador de canciones"""
    global _song_generator
    if _song_generator is None and SongGenerator is not None:
        _song_generator = SongGenerator()
    return _song_generator


# ============================================================================
# LIFESPAN DEL SERVIDOR
# ============================================================================

@asynccontextmanager
async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
    """Maneja el ciclo de vida del servidor"""
    try:
        logger.info("=" * 60)
        logger.info("AbletonMCP-AI Server v2 iniciando...")
        logger.info("=" * 60)

        # Intentar conectar a Ableton
        try:
            ableton = get_ableton_connection()
            if ableton.connect():
                logger.info("Conectado a Ableton Live")
            else:
                logger.warning("No se pudo conectar a Ableton (¿está abierto el script?)")
        except Exception as e:
            logger.warning(f"Error conectando a Ableton: {e}")

        # Inicializar conexión con Max
        try:
            get_max_connection()
            logger.info(f"Conexión UDP con Max lista en puerto {DEFAULT_MAX_PORT}")
        except Exception as e:
            logger.warning(f"Error inicializando conexión con Max: {e}")

        # Inicializar índice de samples
        try:
            sample_index = get_sample_index()
            if sample_index:
                logger.info(f"Índice de samples cargado: {len(sample_index.samples)} samples")
            else:
                logger.warning("Índice de samples no disponible")
        except Exception as e:
            logger.warning(f"Error cargando índice de samples: {e}")

        # Inicializar generador de canciones
        try:
            song_gen = get_song_generator()
            if song_gen:
                logger.info("Generador de canciones listo")
            else:
                logger.warning("Generador de canciones no disponible")
        except Exception as e:
            logger.warning(f"Error inicializando generador: {e}")

        yield {
            "ableton": _ableton_connection,
            "max": _max_connection,
            "samples": _sample_index,
            "generator": _song_generator
        }

    finally:
        global _ableton_connection, _max_connection
        if _ableton_connection:
            logger.info("Desconectando de Ableton...")
            _ableton_connection.disconnect()
        if _max_connection and _max_connection.sock:
            logger.info("Cerrando socket UDP...")
            _max_connection.sock.close()
        logger.info("AbletonMCP-AI Server v2 detenido")


# ============================================================================
# CREAR SERVIDOR MCP
# ============================================================================

mcp = FastMCP(
    "AbletonMCP-AI-v2",
    instructions=PRODUCER_INSTRUCTIONS,
    lifespan=server_lifespan
)


# ============================================================================
# HERRAMIENTAS MCP - GENERACIÓN DE CANCIONES
# ============================================================================

@mcp.tool()
def generate_song(
    ctx: Context,
    genre: str = "house",
    style: str = "",
    bpm: float = 0,
    key: str = "",
    structure: str = "standard"
) -> str:
    """
    Genera una canción completa con estructura profesional

    Args:
        genre: Género musical (techno, house, trance, tech-house, drum-and-bass)
        style: Sub-género o estilo específico (e.g., "industrial", "deep", "90s", "minimal")
        bpm: BPM deseado (0 = auto-seleccionar según género)
        key: Tonalidad (e.g., "Am", "F#m", "C") - vacío = auto-seleccionar
        structure: Estructura del track (standard, minimal, extended)

    Returns:
        Resumen de la canción generada

    Ejemplos:
        generate_song("techno", "industrial", 138, "F#m")
        generate_song("house", "deep", 124, "Am")
        generate_song("tech-house", "groovy", 126)
    """
    try:
        generator = get_song_generator()
        if not generator:
            return "Error: Generador de canciones no disponible"

        ableton = get_ableton_connection()
        if not ableton.connect():
            return f"Error: No se pudo conectar a Ableton en {ABLETON_HOST}:{DEFAULT_ABLETON_PORT}"

        # Generar configuración
        config = generator.generate_config(genre, style, bpm, key, structure)

        # Enviar comando a Ableton
        response = ableton.send_command("generate_complete_song", {
            "genre": genre,
            "style": style or config.get('style', ''),
            "bpm": config.get('bpm', 120),
            "key": config.get('key', ''),
            "structure": structure
        })

        if response.get("status") == "success":
            summary = config.get("summary", "")
            return f"Canción generada exitosamente!\n{summary}"
        else:
            return f"Error generando canción: {response.get('message', 'Error desconocido')}"

    except Exception as e:
        logger.exception("Error en generate_song")
        return f"Error: {str(e)}"


@mcp.tool()
def load_sample_kit(
    ctx: Context,
    genre: str = "techno",
    key: str = "",
    bpm: int = 0
) -> str:
    """
    Carga un kit de samples completo para un género específico

    Args:
        genre: Género musical para seleccionar samples apropiados
        key: Tonalidad preferida para samples armónicos
        bpm: BPM preferido para samples con tempo específico

    Returns:
        Lista de samples cargados
    """
    try:
        sample_index = get_sample_index()
        if not sample_index:
            return "Error: Índice de samples no disponible"

        max_conn = get_max_connection()

        # Obtener pack de samples
        kit = sample_index.get_sample_pack(genre, key, bpm)

        # Contar samples encontrados
        total_samples = sum(len(samples) for samples in kit.values())

        if total_samples == 0:
            return f"No se encontraron samples para el género '{genre}'"

        # Enviar a Max
        if max_conn.send_sample_kit(kit):
            # Construir resumen
            lines = [f"Kit de samples para {genre} cargado:", ""]
            for category, samples in kit.items():
                if samples:
                    lines.append(f"{category.upper()}:")
                    for s in samples[:2]:  # Mostrar máximo 2 por categoría
                        lines.append(f"  - {s['name']}")
                    if len(samples) > 2:
                        lines.append(f"  ... y {len(samples)-2} más")
            lines.append("")
            lines.append(f"Total: {total_samples} samples enviados a Max")
            return "\n".join(lines)
        else:
            return "Error enviando kit a Max for Live"

    except Exception as e:
        logger.exception("Error en load_sample_kit")
        return f"Error: {str(e)}"


@mcp.tool()
def create_pattern(
    ctx: Context,
    instrument: str,
    pattern_type: str = "standard",
    track_index: int = -1,
    clip_index: int = 0,
    length: float = 4.0,
    key: str = "Am",
    genre: str = "techno"
) -> str:
    """
    Crea un patrón MIDI para un instrumento específico

    Args:
        instrument: Tipo de instrumento (kick, snare, hat, clap, bass, chords, lead, melody)
        pattern_type: Tipo de patrón (standard, minimal, full, complex, simple)
        track_index: Índice del track (-1 = crear nuevo)
        clip_index: Índice del clip/slot
        length: Duración en beats
        key: Tonalidad para instrumentos melódicos
        genre: Género para estilo del patrón

    Returns:
        Confirmación del patrón creado
    """
    try:
        generator = get_song_generator()
        if not generator:
            return "Error: Generador no disponible"

        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        # Crear track si es necesario
        if track_index < 0:
            response = ableton.send_command("create_midi_track", {"index": -1})
            if response.get("status") == "success":
                track_index = response.get("result", {}).get("index", 0)
            else:
                return "Error creando track MIDI"

        # Crear clip
        clip_response = ableton.send_command("create_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "length": length
        })

        if clip_response.get("status") != "success":
            return f"Error creando clip: {clip_response.get('message')}"

        # Generar notas según instrumento
        notes = []
        color = TRACK_COLORS.get(instrument.lower(), 0)

        if instrument.lower() in ['kick', 'bd', 'bass drum']:
            notes = generator._create_kick_pattern(genre, pattern_type)
        elif instrument.lower() in ['snare', 'sd', 'clap']:
            notes = generator._create_clap_pattern(genre, pattern_type)
        elif instrument.lower() in ['hat', 'hihat', 'hh']:
            notes = generator._create_hat_pattern(genre, pattern_type)
        elif instrument.lower() in ['perc', 'percussion']:
            notes = generator._create_perc_pattern(genre, pattern_type)
        elif instrument.lower() == 'bass':
            notes = generator.create_bassline(key, pattern_type, length)
        elif instrument.lower() in ['chords', 'chord', 'pads']:
            notes = generator.create_chord_progression(key, genre, length)
        elif instrument.lower() in ['lead', 'melody', 'synth']:
            notes = generator.create_melody(key, 'minor', length, genre)
        else:
            return f"Instrumento '{instrument}' no reconocido"

        # Aplicar color al track
        if color:
            ableton.send_command("set_track_color", {
                "track_index": track_index,
                "color": color
            })

        # Agregar notas
        notes_response = ableton.send_command("add_notes_to_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "notes": notes
        })

        if notes_response.get("status") == "success":
            return f"Patrón '{pattern_type}' para {instrument} creado en track {track_index}, clip {clip_index} ({len(notes)} notas)"
        else:
            return f"Error agregando notas: {notes_response.get('message')}"

    except Exception as e:
        logger.exception("Error en create_pattern")
        return f"Error: {str(e)}"


@mcp.tool()
def control_transport(
    ctx: Context,
    action: str,
    tempo: float = None
) -> str:
    """
    Controla el transporte de Ableton (play, stop, tempo)

    Args:
        action: Acción a ejecutar (play, stop, continue, toggle, set_tempo)
        tempo: BPM a establecer (solo para action='set_tempo')

    Returns:
        Confirmación de la acción
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        action = action.lower()

        if action == "play":
            response = ableton.send_command("start_playback")
            if response.get("status") == "success":
                return "Reproducción iniciada"
        elif action == "stop":
            response = ableton.send_command("stop_playback")
            if response.get("status") == "success":
                return "Reproducción detenida"
        elif action == "continue":
            response = ableton.send_command("continue_playback")
            if response.get("status") == "success":
                return "Reproducción continuada"
        elif action in ["set_tempo", "tempo", "bpm"]:
            if tempo is None or tempo <= 0:
                return "Error: Debes especificar un tempo válido"
            response = ableton.send_command("set_tempo", {"tempo": tempo})
            if response.get("status") == "success":
                return f"Tempo establecido a {tempo} BPM"
        elif action == "get_tempo":
            response = ableton.send_command("get_session_info")
            if response.get("status") == "success":
                return f"Tempo actual: {response.get('result', {}).get('tempo', 'desconocido')} BPM"
        else:
            return f"Acción '{action}' no reconocida. Usa: play, stop, continue, set_tempo"

        return f"Error: {response.get('message', 'Error desconocido')}"

    except Exception as e:
        logger.exception("Error en control_transport")
        return f"Error: {str(e)}"


@mcp.tool()
def get_session_info(ctx: Context) -> str:
    """
    Obtiene información completa de la sesión actual de Ableton

    Returns:
        JSON con información de la sesión (tempo, tracks, estado de reproducción)
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return f"Error: No conectado a Ableton en {ABLETON_HOST}:{DEFAULT_ABLETON_PORT}"

        response = ableton.send_command("get_session_info")

        if response.get("status") == "success":
            result = response.get("result", {})
            info_lines = [
                "Información de la sesión:",
                f"  Tempo: {result.get('tempo', 'N/A')} BPM",
                f"  Reproduciendo: {'Sí' if result.get('is_playing') else 'No'}",
                f"  Tracks: {result.get('num_tracks', 'N/A')}",
            ]
            if 'current_song_time' in result:
                info_lines.append(f"  Tiempo: {result.get('current_song_time')} beats")
            return "\n".join(info_lines)
        else:
            return f"Error: {response.get('message', 'Error desconocido')}"

    except Exception as e:
        logger.exception("Error en get_session_info")
        return f"Error: {str(e)}"


# ============================================================================
# HERRAMIENTAS MCP - GESTIÓN DE SAMPLES
# ============================================================================

@mcp.tool()
def search_samples(
    ctx: Context,
    query: str,
    category: str = "",
    limit: int = 10
) -> str:
    """
    Busca samples en la librería local

    Args:
        query: Término de búsqueda (e.g., "kick", "bass", "hat")
        category: Categoría (kick, snare, hat, bass, synth, percussion, vocal)
        limit: Número máximo de resultados

    Returns:
        Lista de samples encontrados
    """
    try:
        sample_index = get_sample_index()
        if not sample_index:
            return "Error: Índice de samples no disponible"

        results = sample_index.search(query, category, limit)

        if not results:
            return f"No se encontraron samples para '{query}'"

        output = [f"Samples encontrados para '{query}':\n"]
        for i, sample in enumerate(results, 1):
            output.append(f"{i}. {sample['name']} ({sample['category']})")
            output.append(f"   Path: {sample['path']}")
            if sample.get('key'):
                output.append(f"   Key: {sample['key']}, BPM: {sample.get('bpm', 'N/A')}")
            output.append("")

        return "\n".join(output)

    except Exception as e:
        logger.exception("Error en search_samples")
        return f"Error: {str(e)}"


@mcp.tool()
def get_random_sample(
    ctx: Context,
    category: str = ""
) -> str:
    """
    Obtiene un sample aleatorio de la librería

    Args:
        category: Categoría opcional para filtrar

    Returns:
        Información del sample seleccionado
    """
    try:
        sample_index = get_sample_index()
        if not sample_index:
            return "Error: Índice de samples no disponible"

        sample = sample_index.get_random_sample(category)

        if not sample:
            return f"No hay samples disponibles{' en categoría ' + category if category else ''}"

        return f"""Sample aleatorio seleccionado:
Nombre: {sample['name']}
Categoría: {sample['category']}
Path: {sample['path']}
Key: {sample.get('key', 'N/A')}
BPM: {sample.get('bpm', 'N/A')}"""

    except Exception as e:
        logger.exception("Error en get_random_sample")
        return f"Error: {str(e)}"


@mcp.tool()
def send_sample_to_max(
    ctx: Context,
    sample_path: str,
    track_index: int = 0,
    slot: int = 0
) -> str:
    """
    Envía una ruta de sample a Max for Live para cargar

    Args:
        sample_path: Ruta completa del archivo de audio
        track_index: Índice del track donde cargar
        slot: Slot/clip donde cargar el sample

    Returns:
        Confirmación del envío
    """
    try:
        max_conn = get_max_connection()

        if max_conn.send_sample_path(track_index, sample_path, slot):
            return f"Sample enviado a Max: {Path(sample_path).name} -> Track {track_index}, Slot {slot}"
        else:
            return "Error enviando sample a Max"

    except Exception as e:
        logger.exception("Error en send_sample_to_max")
        return f"Error: {str(e)}"


@mcp.tool()
def refresh_sample_index(ctx: Context) -> str:
    """
    Refresca el índice de samples escaneando el directorio nuevamente

    Returns:
        Confirmación con el número de samples encontrados
    """
    try:
        global _sample_index
        if SampleIndex is None:
            return "Error: Módulo SampleIndex no disponible"

        _sample_index = SampleIndex(SAMPLES_DIR)
        _sample_index.refresh()

        return f"Índice refrescado: {len(_sample_index.samples)} samples encontrados"

    except Exception as e:
        logger.exception("Error en refresh_sample_index")
        return f"Error: {str(e)}"


# ============================================================================
# HERRAMIENTAS MCP - CREACIÓN AVANZADA
# ============================================================================

@mcp.tool()
def create_drum_pattern(
    ctx: Context,
    track_index: int,
    clip_index: int,
    style: str = "techno",
    pattern_type: str = "full",
    length: float = 4.0
) -> str:
    """
    Crea un patrón de batería completo

    Args:
        track_index: Índice del track MIDI donde crear el patrón
        clip_index: Índice del clip/slot
        style: Estilo (techno, house, trance, minimal)
        pattern_type: Tipo de patrón (full, kick-only, hats-only, minimal)
        length: Duración en beats

    Returns:
        Confirmación del patrón creado
    """
    try:
        generator = get_song_generator()
        if not generator:
            return "Error: Generador no disponible"

        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        notes = generator.create_drum_pattern(style, pattern_type, length)

        # Crear clip
        clip_response = ableton.send_command("create_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "length": length
        })

        if clip_response.get("status") != "success":
            return f"Error creando clip: {clip_response.get('message')}"

        # Agregar notas
        notes_response = ableton.send_command("add_notes_to_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "notes": notes
        })

        if notes_response.get("status") == "success":
            return f"Patrón de batería '{style}' creado ({len(notes)} notas)"
        else:
            return f"Error agregando notas: {notes_response.get('message')}"

    except Exception as e:
        logger.exception("Error en create_drum_pattern")
        return f"Error: {str(e)}"


@mcp.tool()
def create_bassline(
    ctx: Context,
    track_index: int,
    clip_index: int,
    key: str,
    style: str = "rolling",
    length: float = 16.0
) -> str:
    """
    Crea una línea de bajo musical

    Args:
        track_index: Índice del track MIDI
        clip_index: Índice del clip
        key: Tonalidad (e.g., "Am", "F#m", "C")
        style: Estilo (rolling, minimal, acid, walking, offbeat)
        length: Duración en beats

    Returns:
        Confirmación del bassline creado
    """
    try:
        generator = get_song_generator()
        if not generator:
            return "Error: Generador no disponible"

        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        notes = generator.create_bassline(key, style, length)

        # Crear clip
        clip_response = ableton.send_command("create_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "length": length
        })

        if clip_response.get("status") != "success":
            return f"Error creando clip: {clip_response.get('message')}"

        # Agregar notas
        notes_response = ableton.send_command("add_notes_to_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "notes": notes
        })

        if notes_response.get("status") == "success":
            return f"Bassline '{style}' en {key} creado ({len(notes)} notas)"
        else:
            return f"Error agregando notas: {notes_response.get('message')}"

    except Exception as e:
        logger.exception("Error en create_bassline")
        return f"Error: {str(e)}"


@mcp.tool()
def create_chord_progression(
    ctx: Context,
    track_index: int,
    clip_index: int,
    key: str,
    progression_type: str = "techno",
    length: float = 16.0
) -> str:
    """
    Crea una progresión de acordes

    Args:
        track_index: Índice del track MIDI
        clip_index: Índice del clip
        key: Tonalidad (e.g., "Am", "F#m", "C")
        progression_type: Tipo (techno, house, deep, minor)
        length: Duración en beats (usualmente 16 = 4 compases)

    Returns:
        Confirmación de la progresión creada
    """
    try:
        generator = get_song_generator()
        if not generator:
            return "Error: Generador no disponible"

        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        notes = generator.create_chord_progression(key, progression_type, length)

        # Crear clip
        clip_response = ableton.send_command("create_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "length": length
        })

        if clip_response.get("status") != "success":
            return f"Error creando clip: {clip_response.get('message')}"

        # Agregar notas
        notes_response = ableton.send_command("add_notes_to_clip", {
            "track_index": track_index,
            "clip_index": clip_index,
            "notes": notes
        })

        if notes_response.get("status") == "success":
            return f"Progresión '{progression_type}' en {key} creada ({len(notes)} notas)"
        else:
            return f"Error agregando notas: {notes_response.get('message')}"

    except Exception as e:
        logger.exception("Error en create_chord_progression")
        return f"Error: {str(e)}"


# ============================================================================
# HERRAMIENTAS MCP - GESTIÓN DE TRACKS Y CLIPS
# ============================================================================

@mcp.tool()
def create_midi_track(
    ctx: Context,
    name: str = "MIDI Track",
    color: int = None
) -> str:
    """
    Crea un nuevo track MIDI

    Args:
        name: Nombre del track
        color: Color del track (0-69, opcional)

    Returns:
        Confirmación con el índice del track creado
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        response = ableton.send_command("create_midi_track", {"index": -1})

        if response.get("status") == "success":
            track_index = response.get("result", {}).get("index", 0)

            # Setear nombre
            ableton.send_command("set_track_name", {
                "track_index": track_index,
                "name": name
            })

            # Setear color si se especificó
            if color is not None:
                ableton.send_command("set_track_color", {
                    "track_index": track_index,
                    "color": color
                })

            return f"Track MIDI '{name}' creado en índice {track_index}"
        else:
            return f"Error: {response.get('message')}"

    except Exception as e:
        logger.exception("Error en create_midi_track")
        return f"Error: {str(e)}"


@mcp.tool()
def create_audio_track(
    ctx: Context,
    name: str = "Audio Track",
    color: int = None
) -> str:
    """
    Crea un nuevo track de audio

    Args:
        name: Nombre del track
        color: Color del track (0-69, opcional)

    Returns:
        Confirmación con el índice del track creado
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        response = ableton.send_command("create_audio_track", {"index": -1})

        if response.get("status") == "success":
            track_index = response.get("result", {}).get("index", 0)

            # Setear nombre
            ableton.send_command("set_track_name", {
                "track_index": track_index,
                "name": name
            })

            # Setear color si se especificó
            if color is not None:
                ableton.send_command("set_track_color", {
                    "track_index": track_index,
                    "color": color
                })

            return f"Track de audio '{name}' creado en índice {track_index}"
        else:
            return f"Error: {response.get('message')}"

    except Exception as e:
        logger.exception("Error en create_audio_track")
        return f"Error: {str(e)}"


@mcp.tool()
def set_track_volume(
    ctx: Context,
    track_index: int,
    volume: float
) -> str:
    """
    Ajusta el volumen de un track (0.0 - 1.0)

    Args:
        track_index: Índice del track
        volume: Volumen entre 0.0 y 1.0

    Returns:
        Confirmación del cambio
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        response = ableton.send_command("set_track_volume", {
            "track_index": track_index,
            "volume": volume
        })

        if response.get("status") == "success":
            return f"Volumen del track {track_index} ajustado a {volume:.2f}"
        else:
            return f"Error: {response.get('message')}"

    except Exception as e:
        logger.exception("Error en set_track_volume")
        return f"Error: {str(e)}"


@mcp.tool()
def fire_clip(
    ctx: Context,
    track_index: int,
    clip_index: int
) -> str:
    """
    Dispara/reproduce un clip específico

    Args:
        track_index: Índice del track
        clip_index: Índice del clip/slot

    Returns:
        Confirmación
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        response = ableton.send_command("fire_clip", {
            "track_index": track_index,
            "clip_index": clip_index
        })

        if response.get("status") == "success":
            return f"Clip en track {track_index}, slot {clip_index} disparado"
        else:
            return f"Error: {response.get('message')}"

    except Exception as e:
        logger.exception("Error en fire_clip")
        return f"Error: {str(e)}"


@mcp.tool()
def fire_scene(
    ctx: Context,
    scene_index: int
) -> str:
    """
    Dispara una scene (todos sus clips)

    Args:
        scene_index: Índice de la scene

    Returns:
        Confirmación
    """
    try:
        ableton = get_ableton_connection()
        if not ableton.connect():
            return "Error: No conectado a Ableton"

        response = ableton.send_command("fire_scene", {
            "scene_index": scene_index
        })

        if response.get("status") == "success":
            return f"Scene {scene_index} disparada"
        else:
            return f"Error: {response.get('message')}"

    except Exception as e:
        logger.exception("Error en fire_scene")
        return f"Error: {str(e)}"


# ============================================================================
# HERRAMIENTAS MCP - UTILIDADES
# ============================================================================

@mcp.tool()
def get_available_samples(ctx: Context) -> str:
    """
    Obtiene un resumen de los samples disponibles en la librería

    Returns:
        Resumen por categorías
    """
    try:
        sample_index = get_sample_index()
        if not sample_index:
            return "Error: Índice de samples no disponible"

        categories = {}
        for sample in sample_index.samples:
            cat = sample['category']
            categories[cat] = categories.get(cat, 0) + 1

        lines = ["Samples disponibles:", ""]
        for cat, count in sorted(categories.items(), key=lambda x: -x[1]):
            lines.append(f"  {cat}: {count}")
        lines.append("")
        lines.append(f"Total: {len(sample_index.samples)} samples")

        return "\n".join(lines)

    except Exception as e:
        logger.exception("Error en get_available_samples")
        return f"Error: {str(e)}"


@mcp.tool()
def test_connections(ctx: Context) -> str:
    """
    Prueba las conexiones con Ableton y Max

    Returns:
        Estado de las conexiones
    """
    results = []

    # Probar Ableton
    try:
        ableton = get_ableton_connection()
        if ableton.connect(timeout=3.0):
            results.append("Ableton: Conectado")
            # Probar comando simple
            resp = ableton.send_command("get_session_info")
            if resp.get("status") == "success":
                results.append(f"  - Tempo: {resp.get('result', {}).get('tempo')} BPM")
                results.append(f"  - Tracks: {resp.get('result', {}).get('num_tracks')}")
        else:
            results.append(f"Ableton: No conectado ({ableton.last_error})")
    except Exception as e:
        results.append(f"Ableton: Error - {e}")

    # Probar Max
    try:
        max_conn = get_max_connection()
        if max_conn.send_message({"type": "ping", "timestamp": datetime.now().isoformat()}):
            results.append(f"Max for Live: Conexión UDP lista en puerto {DEFAULT_MAX_PORT}")
        else:
            results.append("Max for Live: Error enviando mensaje")
    except Exception as e:
        results.append(f"Max for Live: Error - {e}")

    # Probar Samples
    try:
        sample_index = get_sample_index()
        if sample_index:
            results.append(f"Samples: {len(sample_index.samples)} samples indexados")
        else:
            results.append("Samples: Índice no disponible")
    except Exception as e:
        results.append(f"Samples: Error - {e}")

    return "\n".join(results)


# ============================================================================
# MAIN
# ============================================================================

def main():
    """Punto de entrada principal"""
    import argparse

    parser = argparse.ArgumentParser(description="AbletonMCP-AI Server v2")
    parser.add_argument("--port", type=int, default=0, help="Puerto para el servidor MCP (0 = auto)")
    parser.add_argument("--transport", type=str, default="stdio",
                       choices=["stdio", "sse"], help="Transporte MCP")
    parser.add_argument("--test", action="store_true", help="Probar conexiones y salir")
    args = parser.parse_args()

    print("=" * 60)
    print("AbletonMCP-AI Server v2")
    print("=" * 60)
    print(f"Transporte: {args.transport}")
    print(f"Ableton: {ABLETON_HOST}:{DEFAULT_ABLETON_PORT}")
    print(f"Max UDP: {MAX_HOST}:{DEFAULT_MAX_PORT}")
    print(f"Samples: {SAMPLES_DIR}")
    print("-" * 60)

    if args.test:
        print("\nProbando conexiones...")
        # Crear contexto temporal para test
        ctx = Context(request_context={})
        result = test_connections(ctx)
        print(result)
        return

    # Iniciar servidor MCP
    mcp.run(transport=args.transport)


if __name__ == "__main__":
    main()
