#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
task_runner.py
==============
Esegue file di task YAML sul Yamaha TF5 tramite TF5MixerController.

Uso da CLI:
    python task_runner.py tasks_concerto_live.yaml              # lista le cue disponibili
    python task_runner.py tasks_concerto_live.yaml CUE_01       # esegue una cue specifica
    python task_runner.py tasks_concerto_live.yaml --all        # esegue tutte le cue in sequenza
"""

import sys
import time
import yaml
from pathlib import Path
from typing import Optional

from mixer_controller import TF5MixerController


# ──────────────────────────────────────────────────────────────
# DISPATCHER: mappa action → metodo del controller
# ──────────────────────────────────────────────────────────────

class TaskRunner:
    def __init__(self, controller: TF5MixerController):
        self.ctrl = controller

        # Mappa nome azione → callable
        self._action_map = {
            "set_channel_level":        self._set_channel_level,
            "set_channel_on_off":       self._set_channel_on_off,
            "set_channel_pan":          self._set_channel_pan,
            "mute_channels":            self._mute_channels,
            "unmute_channels":          self._unmute_channels,
            "set_mix_level":            self._set_mix_level,
            "set_mix_on_off":           self._set_mix_on_off,
            "set_channel_to_mix_level": self._set_channel_to_mix_level,
            "set_channel_to_mix_on_off":self._set_channel_to_mix_on_off,
            "recall_scene":             self._recall_scene,
            "wait":                     self._wait,
            "refresh_cache":            self._refresh_cache,
        }

    # ── Handlers specifici ──────────────────────────────────

    def _set_channel_level(self, step: dict) -> dict:
        return self.ctrl.set_channel_level(step["channel"], float(step["level_db"]))

    def _set_channel_on_off(self, step: dict) -> dict:
        return self.ctrl.set_channel_on_off(step["channel"], bool(step["state"]))

    def _set_channel_pan(self, step: dict) -> dict:
        return self.ctrl.set_channel_pan(step["channel"], int(step["pan"]))

    def _mute_channels(self, step: dict) -> dict:
        return self.ctrl.mute_multiple_channels(list(step["channels"]))

    def _unmute_channels(self, step: dict) -> dict:
        return self.ctrl.unmute_multiple_channels(list(step["channels"]))

    def _set_mix_level(self, step: dict) -> dict:
        return self.ctrl.set_mix_level(step["mix"], float(step["level_db"]))

    def _set_mix_on_off(self, step: dict) -> dict:
        return self.ctrl.set_mix_on_off(step["mix"], bool(step["state"]))

    def _set_channel_to_mix_level(self, step: dict) -> dict:
        return self.ctrl.set_channel_to_mix_level(step["channel"], step["mix"], float(step["level_db"]))

    def _set_channel_to_mix_on_off(self, step: dict) -> dict:
        return self.ctrl.set_channel_to_mix_on_off(step["channel"], step["mix"], bool(step["state"]))

    def _recall_scene(self, step: dict) -> dict:
        return self.ctrl.recall_scene(str(step["bank"]), int(step["scene"]))

    def _wait(self, step: dict) -> dict:
        ms = int(step.get("ms", 0))
        print(f"  ⏱  Wait puro: {ms} ms")
        time.sleep(ms / 1000.0)
        return {"status": "success", "message": f"Atteso {ms} ms"}

    def _refresh_cache(self, step: dict) -> dict:
        return self.ctrl.refresh_cache()

    # ── Esecuzione di un singolo step ───────────────────────

    def _run_step(self, step_num: int, step: dict) -> bool:
        """Esegue un singolo step. Ritorna True se OK, False se errore."""
        action = step.get("action")
        delay_ms  = int(step.get("delay_ms", 0))
        wait_ms   = int(step.get("wait_ms", 0))

        if delay_ms > 0:
            print(f"    ⏳ delay_ms={delay_ms} prima dell'azione...")
            time.sleep(delay_ms / 1000.0)

        handler = self._action_map.get(action)
        if handler is None:
            print(f"  ⚠️  Step {step_num}: azione '{action}' non riconosciuta — saltato.")
            return False

        print(f"  ▶ Step {step_num}: {action} | params: { {k:v for k,v in step.items() if k not in ('action','delay_ms','wait_ms')} }")
        try:
            result = handler(step)
            status = result.get("status", "unknown")
            msg    = result.get("message", "")
            icon   = "✅" if status in ("success", "partial") else "❌"
            print(f"    {icon} [{status}] {msg}")

            if wait_ms > 0:
                time.sleep(wait_ms / 1000.0)

            return status in ("success", "partial")

        except Exception as e:
            print(f"    ❌ Eccezione durante '{action}': {e}")
            return False

    # ── Esecuzione di una cue ────────────────────────────────

    def run_cue(self, cue: dict) -> bool:
        """
        Esegue tutti gli step di una cue.
        Ritorna True se tutti gli step sono andati a buon fine.
        """
        cue_id   = cue.get("id", "???")
        name     = cue.get("name", "")
        on_error = cue.get("on_error", "stop")
        steps    = cue.get("steps", [])

        print(f"\n{'═'*60}")
        print(f"🎬 CUE: {cue_id} — {name}")
        if cue.get("description"):
            print(f"   📋 {cue['description']}")
        print(f"   Steps: {len(steps)} | on_error: {on_error}")
        print(f"{'─'*60}")

        all_ok = True
        for i, step in enumerate(steps, start=1):
            ok = self._run_step(i, step)
            if not ok:
                all_ok = False
                if on_error == "stop":
                    print(f"\n  🛑 on_error=stop: cue {cue_id} interrotta allo step {i}.")
                    return False
                # on_error == "continue": vai avanti

        result_icon = "✅" if all_ok else "⚠️ (con errori)"
        print(f"\n  {result_icon} Cue {cue_id} completata.")
        return all_ok


# ──────────────────────────────────────────────────────────────
# LOADER YAML
# ──────────────────────────────────────────────────────────────

def load_task_file(path: str) -> dict:
    """Carica e valida un file di task YAML."""
    p = Path(path)
    if not p.exists():
        raise FileNotFoundError(f"File non trovato: {path}")
    with open(p, "r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
    if "cues" not in data or not isinstance(data["cues"], list):
        raise ValueError("Il file YAML deve contenere una lista 'cues'.")
    return data


def print_event_info(data: dict):
    event = data.get("event", {})
    if event:
        print(f"\n{'═'*60}")
        print(f"📅 Evento : {event.get('name', 'N/D')}")
        print(f"   Data   : {event.get('date', 'N/D')}")
        print(f"   Venue  : {event.get('venue', 'N/D')}")
        if event.get("notes"):
            print(f"   Note   : {event['notes']}")


def list_cues(data: dict):
    print_event_info(data)
    print(f"\n{'═'*60}")
    print("📋 CUE DISPONIBILI:")
    print(f"{'─'*60}")
    for cue in data["cues"]:
        steps_count = len(cue.get("steps", []))
        print(f"  {cue.get('id'):12s} │ {cue.get('name','')} ({steps_count} steps)")
    print(f"{'═'*60}\n")


# ──────────────────────────────────────────────────────────────
# ENTRY POINT
# ──────────────────────────────────────────────────────────────

def main():
    if len(sys.argv) < 2:
        print(__doc__)
        sys.exit(1)

    task_file = sys.argv[1]
    cue_arg   = sys.argv[2] if len(sys.argv) > 2 else None

    try:
        data = load_task_file(task_file)
    except (FileNotFoundError, ValueError) as e:
        print(f"❌ Errore nel caricamento del file: {e}")
        sys.exit(1)

    # Solo lista cue senza mixer
    if cue_arg is None:
        list_cues(data)
        sys.exit(0)

    # Connessione al mixer ed esecuzione
    print("🔌 Connessione al mixer...")
    import config

    
    with TF5MixerController(host=config.get_host()) as ctrl:
        runner = TaskRunner(ctrl)

        if cue_arg == "--all":
            print_event_info(data)
            print("\n▶▶ Esecuzione di TUTTE le cue in sequenza...")
            for cue in data["cues"]:
                ok = runner.run_cue(cue)
                if not ok and cue.get("on_error", "stop") == "stop":
                    print(f"\n🛑 Esecuzione interrotta a causa di un errore in {cue.get('id')}.")
                    sys.exit(1)
        else:
            # Cerca la cue per ID
            cue_found = next((c for c in data["cues"] if c.get("id") == cue_arg), None)
            if cue_found is None:
                print(f"❌ Cue '{cue_arg}' non trovata nel file.")
                list_cues(data)
                sys.exit(1)
            runner.run_cue(cue_found)

    print("\n✅ Task runner completato.\n")


if __name__ == "__main__":
    main()
