#!/usr/bin/env python3
"""
triggers.py - Jazz Event Triggers
==================================

Jazz Triggers fire reflexes based on events (port activity, patterns, etc).

Consolidated from:
- receipts/.../tools/net/jazz_reflex_trigger__v20.py
- port_braid_events.sym watchers

WO#NET_JAZZ_00
"""

from __future__ import annotations
import re
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Optional, Callable, List, Dict, Any
import time


@dataclass
class JazzTrigger:
    """A Jazz reflex trigger."""
    trigger_id: str
    pattern: str
    action: str
    fired_at: Optional[str] = None
    metadata: Dict[str, Any] = field(default_factory=dict)


def trigger_on_pattern(
    content: str,
    pattern: str,
    action: Callable[[str], None] = None,
) -> List[JazzTrigger]:
    """
    Fire triggers for all pattern matches in content.

    Args:
        content: Content to search
        pattern: Regex pattern to match
        action: Callback for each match

    Returns:
        List of fired triggers
    """
    triggers = []
    matches = re.findall(pattern, content)

    for i, match in enumerate(matches):
        trigger = JazzTrigger(
            trigger_id=f"trigger_{datetime.now().strftime('%Y%m%d%H%M%S')}_{i}",
            pattern=pattern,
            action=f"match:{match}",
            fired_at=datetime.now().isoformat(),
            metadata={"match": match, "index": i},
        )
        triggers.append(trigger)

        if action:
            action(match)

    return triggers


def trigger_on_ports(
    content: str,
    on_trigger: Optional[Callable[[str, int], None]] = None,
) -> List[JazzTrigger]:
    """
    Fire triggers for port references in content.

    Looks for patterns like "port_21", "::PORTS::", etc.

    Args:
        content: Content to search
        on_trigger: Callback with (line, port_num)

    Returns:
        List of fired triggers
    """
    triggers = []

    # Pattern: port_N or ::PORTS::
    port_pattern = r'port_(\d+)|::PORTS::'

    for line in content.splitlines():
        matches = re.findall(r'port_(\d+)', line)
        if matches or "::PORTS::" in line:
            for port_str in matches:
                port_num = int(port_str)
                trigger = JazzTrigger(
                    trigger_id=f"port_trigger_{port_num}_{datetime.now().strftime('%H%M%S')}",
                    pattern="port_N",
                    action=f"port:{port_num}",
                    fired_at=datetime.now().isoformat(),
                    metadata={"port": port_num, "line": line[:100]},
                )
                triggers.append(trigger)

                if on_trigger:
                    on_trigger(line, port_num)

            if "::PORTS::" in line and not matches:
                trigger = JazzTrigger(
                    trigger_id=f"ports_tag_{datetime.now().strftime('%H%M%S')}",
                    pattern="::PORTS::",
                    action="ports_broadcast",
                    fired_at=datetime.now().isoformat(),
                    metadata={"line": line[:100]},
                )
                triggers.append(trigger)

                if on_trigger:
                    on_trigger(line, 0)

    return triggers


def watch_braid_events(
    events_path: Path,
    log_path: Optional[Path] = None,
    on_trigger: Optional[Callable[[JazzTrigger], None]] = None,
    tail_lines: int = 10,
) -> Dict[str, Any]:
    """
    Watch braid events file and fire triggers.

    Consolidated from jazz_reflex_trigger__v20.py behavior.

    Args:
        events_path: Path to port_braid_events.sym or similar
        log_path: Optional path to write trigger log
        on_trigger: Callback for each trigger
        tail_lines: Number of recent lines to check

    Returns:
        Dict with watch results
    """
    if not events_path.exists():
        return {
            "watched": False,
            "error": f"Events file not found: {events_path}",
            "timestamp": datetime.now().isoformat(),
        }

    lines = events_path.read_text(errors='ignore').splitlines()
    recent = lines[-tail_lines:] if len(lines) > tail_lines else lines

    all_triggers = []
    for line in recent:
        if "::PORTS::" in line:
            triggers = trigger_on_ports(line, on_trigger=lambda l, p: None)
            all_triggers.extend(triggers)

            # Log to file if specified
            if log_path:
                log_path.parent.mkdir(parents=True, exist_ok=True)
                with log_path.open("a") as f:
                    f.write(f"[{datetime.now().isoformat()}] ::MUTATE:: Jazz reflex triggered via ports -> {line}\n")

            if on_trigger:
                for t in triggers:
                    on_trigger(t)

    return {
        "watched": True,
        "events_path": str(events_path),
        "lines_checked": len(recent),
        "triggers_fired": len(all_triggers),
        "triggers": [{"id": t.trigger_id, "action": t.action} for t in all_triggers],
        "timestamp": datetime.now().isoformat(),
    }


def watch_continuous(
    events_path: Path,
    poll_interval: float = 1.0,
    log_path: Optional[Path] = None,
    on_trigger: Optional[Callable[[JazzTrigger], None]] = None,
) -> None:
    """
    Continuously watch braid events file.

    Args:
        events_path: Path to events file
        poll_interval: Seconds between polls
        log_path: Optional log path
        on_trigger: Callback for triggers
    """
    print(f"[JAZZ_TRIGGER] Watching: {events_path}")
    print(f"[JAZZ_TRIGGER] Poll interval: {poll_interval}s")

    last_size = 0
    while True:
        try:
            if events_path.exists():
                current_size = events_path.stat().st_size
                if current_size != last_size:
                    result = watch_braid_events(
                        events_path,
                        log_path=log_path,
                        on_trigger=on_trigger,
                    )
                    if result["triggers_fired"] > 0:
                        print(f"[JAZZ_TRIGGER] Fired {result['triggers_fired']} triggers")
                    last_size = current_size

            time.sleep(poll_interval)

        except KeyboardInterrupt:
            print("[JAZZ_TRIGGER] Stopped")
            break


# ============================================================================
# CLI
# ============================================================================

def _cli():
    """Jazz Triggers CLI."""
    import argparse

    parser = argparse.ArgumentParser(description="Jazz Event Triggers")
    parser.add_argument("--events", type=Path, help="Events file to watch")
    parser.add_argument("--log", type=Path, help="Log file for triggers")
    parser.add_argument("--continuous", action="store_true", help="Continuous watching")
    parser.add_argument("--poll", type=float, default=1.0, help="Poll interval")
    args = parser.parse_args()

    if args.events:
        if args.continuous:
            watch_continuous(
                args.events,
                poll_interval=args.poll,
                log_path=args.log,
                on_trigger=lambda t: print(f"[TRIGGER] {t.trigger_id}: {t.action}"),
            )
        else:
            result = watch_braid_events(args.events, log_path=args.log)
            print(f"[JAZZ_TRIGGER] Result: {result}")
    else:
        parser.print_help()


if __name__ == "__main__":
    _cli()
