#!/usr/bin/env python3
"""
Luz Orchestrator Daemon

Background service that:
1. Monitors a task queue for incoming requests
2. Routes tasks to appropriate project subagents
3. Manages resource usage and concurrency
4. Provides health monitoring

This replaces multiple persistent Claude sessions with
on-demand subagent execution.
"""

import json
import os
import sys
import time
import logging
import signal
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Any
from dataclasses import dataclass, asdict
from queue import Queue, Empty
from threading import Thread, Event
import socket

# Configuration
CONFIG_PATH = Path(__file__).parent / "config.json"
TASK_QUEUE_PATH = Path("/var/run/luz-orchestrator/tasks.json")
LOG_DIR = Path("/var/log/luz-orchestrator")
PID_FILE = Path("/var/run/luz-orchestrator/daemon.pid")
SOCKET_PATH = Path("/var/run/luz-orchestrator/orchestrator.sock")

# Ensure directories exist
LOG_DIR.mkdir(parents=True, exist_ok=True)
TASK_QUEUE_PATH.parent.mkdir(parents=True, exist_ok=True)

# Logging setup
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler(LOG_DIR / "daemon.log"),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

@dataclass
class Task:
    id: str
    project: Optional[str]
    prompt: str
    tools: list
    model: str
    status: str = "pending"
    result: Optional[str] = None
    created_at: str = ""
    completed_at: str = ""

    def __post_init__(self):
        if not self.created_at:
            self.created_at = datetime.now().isoformat()

class OrchestratorDaemon:
    def __init__(self):
        self.config = self._load_config()
        self.task_queue: Queue = Queue()
        self.stop_event = Event()
        self.active_tasks: Dict[str, Task] = {}
        self.completed_tasks: list = []
        self.max_completed = 100  # Keep last 100 completed tasks

    def _load_config(self) -> dict:
        """Load configuration from file"""
        if CONFIG_PATH.exists():
            with open(CONFIG_PATH) as f:
                return json.load(f)
        return {"projects": {}, "orchestrator": {}}

    def _save_pid(self):
        """Save PID file"""
        with open(PID_FILE, 'w') as f:
            f.write(str(os.getpid()))

    def _remove_pid(self):
        """Remove PID file"""
        if PID_FILE.exists():
            PID_FILE.unlink()

    def detect_project(self, prompt: str) -> Optional[str]:
        """Detect which project a prompt relates to"""
        prompt_lower = prompt.lower()

        # Check direct mentions
        for name in self.config.get("projects", {}):
            if name in prompt_lower:
                return name

        # Check path mentions
        for name, cfg in self.config.get("projects", {}).items():
            if cfg.get("path", "") in prompt:
                return name

        return None

    def run_subagent(self, task: Task) -> str:
        """Execute a task using Claude subagent"""
        project_config = self.config.get("projects", {}).get(task.project, {})
        cwd = project_config.get("path", "/home/admin")
        focus = project_config.get("focus", "")

        # Build context-aware prompt
        full_prompt = f"""You are a subagent for the {task.project or 'general'} project.
Working directory: {cwd}
Focus: {focus}

Task: {task.prompt}

Execute efficiently and return a concise summary."""

        try:
            result = subprocess.run(
                [
                    "claude",
                    "-p", full_prompt,
                    "--output-format", "json",
                    "--allowedTools", ",".join(task.tools),
                    "--model", task.model
                ],
                cwd=cwd,
                capture_output=True,
                text=True,
                timeout=300
            )

            return result.stdout if result.returncode == 0 else f"Error: {result.stderr}"

        except subprocess.TimeoutExpired:
            return "Error: Task timed out after 5 minutes"
        except Exception as e:
            return f"Error: {str(e)}"

    def process_task(self, task: Task):
        """Process a single task"""
        logger.info(f"Processing task {task.id}: {task.prompt[:50]}...")

        task.status = "running"
        self.active_tasks[task.id] = task

        try:
            result = self.run_subagent(task)
            task.result = result
            task.status = "completed"
        except Exception as e:
            task.result = str(e)
            task.status = "failed"

        task.completed_at = datetime.now().isoformat()

        # Move to completed
        del self.active_tasks[task.id]
        self.completed_tasks.append(task)

        # Trim completed tasks
        if len(self.completed_tasks) > self.max_completed:
            self.completed_tasks = self.completed_tasks[-self.max_completed:]

        logger.info(f"Task {task.id} {task.status}")

    def worker_loop(self):
        """Main worker loop processing tasks"""
        while not self.stop_event.is_set():
            try:
                task = self.task_queue.get(timeout=1.0)
                self.process_task(task)
            except Empty:
                continue
            except Exception as e:
                logger.error(f"Worker error: {e}")

    def submit_task(self, prompt: str, project: Optional[str] = None,
                    tools: Optional[list] = None, model: str = "haiku") -> str:
        """Submit a new task to the queue"""
        task_id = f"task_{int(time.time() * 1000)}"

        if not project:
            project = self.detect_project(prompt)

        project_config = self.config.get("projects", {}).get(project, {})
        default_tools = project_config.get("tools", ["Read", "Glob", "Grep", "Bash"])

        task = Task(
            id=task_id,
            project=project,
            prompt=prompt,
            tools=tools or default_tools,
            model=model
        )

        self.task_queue.put(task)
        logger.info(f"Submitted task {task_id} for project {project}")

        return task_id

    def get_status(self) -> dict:
        """Get daemon status"""
        return {
            "running": True,
            "pid": os.getpid(),
            "queue_size": self.task_queue.qsize(),
            "active_tasks": len(self.active_tasks),
            "completed_tasks": len(self.completed_tasks),
            "projects": list(self.config.get("projects", {}).keys()),
            "uptime": time.time() - self.start_time
        }

    def handle_signal(self, signum, frame):
        """Handle shutdown signals"""
        logger.info(f"Received signal {signum}, shutting down...")
        self.stop_event.set()

    def run(self):
        """Run the daemon"""
        logger.info("Starting Luz Orchestrator Daemon")

        # Set up signal handlers
        signal.signal(signal.SIGTERM, self.handle_signal)
        signal.signal(signal.SIGINT, self.handle_signal)

        self._save_pid()
        self.start_time = time.time()

        # Start worker threads
        workers = []
        max_workers = self.config.get("orchestrator", {}).get("max_concurrent_subagents", 3)

        for i in range(max_workers):
            worker = Thread(target=self.worker_loop, name=f"worker-{i}")
            worker.daemon = True
            worker.start()
            workers.append(worker)

        logger.info(f"Started {max_workers} worker threads")

        # Main loop - could add socket server for IPC here
        try:
            while not self.stop_event.is_set():
                time.sleep(1)
        finally:
            self._remove_pid()
            logger.info("Daemon stopped")

def main():
    import argparse

    parser = argparse.ArgumentParser(description="Luz Orchestrator Daemon")
    parser.add_argument("--status", action="store_true", help="Check daemon status")
    parser.add_argument("--submit", help="Submit a task")
    parser.add_argument("--project", help="Target project for task")

    args = parser.parse_args()

    if args.status:
        if PID_FILE.exists():
            pid = int(PID_FILE.read_text().strip())
            try:
                os.kill(pid, 0)  # Check if process exists
                print(f"Daemon running (PID: {pid})")
            except OSError:
                print("Daemon not running (stale PID file)")
        else:
            print("Daemon not running")
        return

    if args.submit:
        # For now, just run the task directly
        # In production, would connect to daemon via socket
        daemon = OrchestratorDaemon()
        task_id = daemon.submit_task(args.submit, args.project)
        print(f"Submitted: {task_id}")
        return

    # Run daemon
    daemon = OrchestratorDaemon()
    daemon.run()

if __name__ == "__main__":
    main()
