#!/usr/bin/env python3
"""
Luzia Status Event Publisher
Real-time status updates from Luzia orchestrator to Claude interface

Usage:
    publisher = LuziaStatusPublisher()
    await publisher.publish_task_started(...)
    await publisher.publish_progress(...)
    await publisher.publish_task_completed(...)
"""

import json
import asyncio
from datetime import datetime
from enum import Enum
from dataclasses import dataclass, asdict, field
from typing import Optional, List, Dict, Any, Callable
from pathlib import Path
import logging

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class StatusMessageType(Enum):
    """Enumeration of all status message types"""
    TASK_STARTED = "TASK_STARTED"
    PROGRESS_UPDATE = "PROGRESS_UPDATE"
    TASK_COMPLETED = "TASK_COMPLETED"
    TASK_QUEUED = "TASK_QUEUED"
    TASK_WARNING = "TASK_WARNING"
    TASK_FAILED = "TASK_FAILED"
    SYSTEM_ALERT = "SYSTEM_ALERT"


class Severity(Enum):
    """Severity levels for status messages"""
    INFO = "info"
    SUCCESS = "success"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"


@dataclass
class StatusMessage:
    """Compact status message for real-time updates"""
    type: StatusMessageType
    task_id: str
    project: str
    timestamp: int

    # Progress fields (optional)
    progress_percent: Optional[int] = None
    current_step: Optional[int] = None
    total_steps: Optional[int] = None
    current_step_name: Optional[str] = None

    # Time fields
    elapsed_seconds: Optional[int] = None
    estimated_remaining_seconds: Optional[int] = None

    # Status fields
    description: Optional[str] = None
    status: Optional[str] = None

    # Warning/Error fields
    alert_type: Optional[str] = None
    error: Optional[str] = None
    warning_type: Optional[str] = None
    message: Optional[str] = None
    recommendation: Optional[str] = None

    # Context
    severity: Severity = Severity.INFO
    queue_position: Optional[int] = None
    queue_ahead: Optional[List[str]] = field(default_factory=list)
    findings_count: Optional[int] = None
    retriable: Optional[bool] = None
    retry_count: Optional[int] = None

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dict, removing None values"""
        result = asdict(self)
        result['type'] = self.type.value
        result['severity'] = self.severity.value
        return {k: v for k, v in result.items() if v is not None}

    def to_json(self) -> str:
        """Convert to JSON"""
        return json.dumps(self.to_dict())

    def to_compact_display(self) -> str:
        """Format for CLI display (one line + details)"""
        emoji_map = {
            StatusMessageType.TASK_STARTED: "🟡",
            StatusMessageType.PROGRESS_UPDATE: "🟢",
            StatusMessageType.TASK_COMPLETED: "✅",
            StatusMessageType.TASK_QUEUED: "🔵",
            StatusMessageType.TASK_WARNING: "⚠️",
            StatusMessageType.TASK_FAILED: "❌",
            StatusMessageType.SYSTEM_ALERT: "⚡",
        }

        emoji = emoji_map.get(self.type, "•")
        task_short = self.task_id.split('-')[-1] if '-' in self.task_id else self.task_id[:8]

        # Build display based on message type
        if self.type == StatusMessageType.TASK_STARTED:
            time_str = (
                f"est. {self.estimated_remaining_seconds//60}m"
                if self.estimated_remaining_seconds
                else "..."
            )
            return (
                f"{emoji} [{self.project}-{task_short}] Starting... ⏱ {time_str}\n"
                f"  └─ {self.description}"
            )

        elif self.type == StatusMessageType.PROGRESS_UPDATE:
            elapsed_m = self.elapsed_seconds // 60
            elapsed_s = self.elapsed_seconds % 60
            remaining_m = (
                self.estimated_remaining_seconds // 60
                if self.estimated_remaining_seconds
                else 0
            )
            remaining_s = (
                self.estimated_remaining_seconds % 60
                if self.estimated_remaining_seconds
                else 0
            )

            lines = [
                f"{emoji} [{self.project}-{task_short}] In Progress - {self.progress_percent}% "
                f"({self.current_step}/{self.total_steps}) ⏱ {elapsed_m}m {elapsed_s}s",
                f"  └─ {self.current_step_name}",
                f"  └─ Est. remaining: {remaining_m}m {remaining_s}s"
            ]
            return "\n".join(lines)

        elif self.type == StatusMessageType.TASK_COMPLETED:
            elapsed_m = self.elapsed_seconds // 60
            elapsed_s = self.elapsed_seconds % 60
            details = []

            if self.findings_count:
                word = "finding" if self.findings_count == 1 else "findings"
                details.append(f"{self.findings_count} {word}")
            if self.recommendation:
                details.append("recommendation")

            detail_str = ", ".join(details) if details else "Done"
            return (
                f"{emoji} [{self.project}-{task_short}] Completed ✓ ({elapsed_m}m {elapsed_s}s)\n"
                f"  └─ {detail_str}"
            )

        elif self.type == StatusMessageType.TASK_QUEUED:
            queue_str = f"{len(self.queue_ahead or [])} ahead"
            wait_m = (
                self.estimated_remaining_seconds // 60
                if self.estimated_remaining_seconds
                else 0
            )
            lines = [
                f"{emoji} [{self.project}-{task_short}] Queued ({queue_str}) ⏱ waiting",
                f"  └─ Reason: {self.message}",
            ]
            if wait_m > 0:
                lines.append(f"  └─ Est. wait: {wait_m}m")
            return "\n".join(lines)

        elif self.type == StatusMessageType.TASK_WARNING:
            elapsed_m = self.elapsed_seconds // 60
            elapsed_s = self.elapsed_seconds % 60
            lines = [
                f"{emoji} [{self.project}-{task_short}] In Progress - {self.progress_percent}% "
                f"({self.current_step}/{self.total_steps}) ⏱ {elapsed_m}m {elapsed_s}s",
                f"  └─ {self.current_step_name}",
                f"  └─ Alert: {self.message}",
            ]
            if self.recommendation:
                lines.append(f"  └─ {self.recommendation}")
            return "\n".join(lines)

        elif self.type == StatusMessageType.TASK_FAILED:
            elapsed_m = self.elapsed_seconds // 60
            elapsed_s = self.elapsed_seconds % 60
            lines = [
                f"{emoji} [{self.project}-{task_short}] Failed ({elapsed_m}m {elapsed_s}s)",
                f"  └─ Error: {self.error}",
            ]
            if self.retriable and self.retry_count:
                lines.append(f"  └─ Auto-retry: Queued (attempt {self.retry_count}/5)")
            return "\n".join(lines)

        elif self.type == StatusMessageType.SYSTEM_ALERT:
            return (
                f"{emoji} SYSTEM ALERT - {self.message}\n"
                f"  └─ Action: {self.recommendation}"
            )

        return f"{emoji} {self.description or 'Status update'}"


class LuziaStatusPublisher:
    """Publishes real-time status updates to Claude interface"""

    def __init__(self, event_handler: Optional[Callable] = None, max_queue_size: int = 100):
        """
        Initialize status publisher

        Args:
            event_handler: Optional async function to call on each event
            max_queue_size: Maximum events in queue before dropping
        """
        self.event_handler = event_handler
        self.event_queue: asyncio.Queue = asyncio.Queue(maxsize=max_queue_size)
        self.active_tasks: Dict[str, Dict[str, Any]] = {}
        self.verbosity_level = "normal"  # quiet, normal, verbose
        self.message_history: List[StatusMessage] = []
        self.max_history = 100

    def set_verbosity(self, level: str):
        """Set how chatty Luzia is: quiet, normal, verbose"""
        if level not in ("quiet", "normal", "verbose"):
            logger.warning(f"Invalid verbosity level: {level}. Using 'normal'")
            level = "normal"
        self.verbosity_level = level

    async def publish_task_started(
        self,
        task_id: str,
        project: str,
        description: str,
        estimated_duration_seconds: int = 300
    ):
        """Task has started"""
        msg = StatusMessage(
            type=StatusMessageType.TASK_STARTED,
            task_id=task_id,
            project=project,
            description=description,
            timestamp=int(datetime.now().timestamp()),
            estimated_remaining_seconds=estimated_duration_seconds,
            severity=Severity.INFO
        )

        self.active_tasks[task_id] = {
            "start_time": datetime.now(),
            "project": project,
            "description": description
        }

        await self._publish(msg)

    async def publish_progress(
        self,
        task_id: str,
        progress_percent: int,
        current_step: int,
        total_steps: int,
        current_step_name: str,
        elapsed_seconds: int,
        estimated_remaining_seconds: int
    ):
        """Update task progress"""
        if task_id not in self.active_tasks:
            logger.warning(f"Progress update for unknown task: {task_id}")
            return

        msg = StatusMessage(
            type=StatusMessageType.PROGRESS_UPDATE,
            task_id=task_id,
            project=self.active_tasks[task_id]["project"],
            progress_percent=progress_percent,
            current_step=current_step,
            total_steps=total_steps,
            current_step_name=current_step_name,
            elapsed_seconds=elapsed_seconds,
            estimated_remaining_seconds=estimated_remaining_seconds,
            timestamp=int(datetime.now().timestamp()),
            severity=Severity.INFO
        )

        # Only publish progress updates based on verbosity level
        should_publish = (
            self.verbosity_level == "verbose"
            or progress_percent % 25 == 0
            or progress_percent == 100
        )

        if should_publish:
            await self._publish(msg)

    async def publish_task_completed(
        self,
        task_id: str,
        elapsed_seconds: int,
        findings_count: int = 0,
        recommendations_count: int = 0,
        status: str = "APPROVED"
    ):
        """Task completed successfully"""
        if task_id not in self.active_tasks:
            logger.warning(f"Completion for unknown task: {task_id}")
            return

        task_info = self.active_tasks[task_id]

        msg = StatusMessage(
            type=StatusMessageType.TASK_COMPLETED,
            task_id=task_id,
            project=task_info["project"],
            description=task_info["description"],
            elapsed_seconds=elapsed_seconds,
            findings_count=findings_count if findings_count > 0 else None,
            timestamp=int(datetime.now().timestamp()),
            status=status,
            severity=Severity.SUCCESS
        )

        await self._publish(msg)
        del self.active_tasks[task_id]

    async def publish_task_queued(
        self,
        task_id: str,
        project: str,
        description: str,
        reason: str,
        queue_position: int,
        queue_ahead: List[str],
        estimated_wait_seconds: int
    ):
        """Task queued waiting for resources"""
        msg = StatusMessage(
            type=StatusMessageType.TASK_QUEUED,
            task_id=task_id,
            project=project,
            description=description,
            message=reason,
            queue_position=queue_position,
            queue_ahead=queue_ahead,
            estimated_remaining_seconds=estimated_wait_seconds,
            timestamp=int(datetime.now().timestamp()),
            severity=Severity.INFO
        )

        await self._publish(msg)

    async def publish_warning(
        self,
        task_id: str,
        warning_type: str,
        message: str,
        current_step: int,
        total_steps: int,
        current_step_name: str,
        elapsed_seconds: int,
        progress_percent: int,
        recommendation: str = None
    ):
        """Task warning (duration exceeded, resource warning, etc)"""
        if task_id not in self.active_tasks:
            logger.warning(f"Warning for unknown task: {task_id}")
            return

        task_info = self.active_tasks[task_id]

        msg = StatusMessage(
            type=StatusMessageType.TASK_WARNING,
            task_id=task_id,
            project=task_info["project"],
            warning_type=warning_type,
            message=message,
            current_step=current_step,
            total_steps=total_steps,
            current_step_name=current_step_name,
            elapsed_seconds=elapsed_seconds,
            progress_percent=progress_percent,
            recommendation=recommendation,
            timestamp=int(datetime.now().timestamp()),
            severity=Severity.WARNING
        )

        await self._publish(msg)

    async def publish_task_failed(
        self,
        task_id: str,
        error: str,
        elapsed_seconds: int,
        retry_count: int = 0,
        retriable: bool = False
    ):
        """Task failed"""
        if task_id not in self.active_tasks:
            logger.warning(f"Failure for unknown task: {task_id}")
            return

        task_info = self.active_tasks[task_id]

        msg = StatusMessage(
            type=StatusMessageType.TASK_FAILED,
            task_id=task_id,
            project=task_info["project"],
            error=error,
            elapsed_seconds=elapsed_seconds,
            retry_count=retry_count if retriable else None,
            retriable=retriable,
            timestamp=int(datetime.now().timestamp()),
            severity=Severity.ERROR
        )

        await self._publish(msg)
        if not retriable:
            del self.active_tasks[task_id]

    async def publish_system_alert(
        self,
        alert_type: str,
        message: str,
        recommendation: str,
        severity: Severity = Severity.WARNING
    ):
        """System-level alert (resource, health, etc)"""
        msg = StatusMessage(
            type=StatusMessageType.SYSTEM_ALERT,
            task_id="system",
            project="luzia",
            alert_type=alert_type,
            message=message,
            recommendation=recommendation,
            timestamp=int(datetime.now().timestamp()),
            severity=severity
        )

        await self._publish(msg)

    async def _publish(self, msg: StatusMessage):
        """Publish message to event queue"""
        try:
            await self.event_queue.put_nowait(msg)

            # Keep history
            self.message_history.append(msg)
            if len(self.message_history) > self.max_history:
                self.message_history = self.message_history[-self.max_history:]

            # Call event handler if provided
            if self.event_handler:
                try:
                    result = self.event_handler(msg)
                    if asyncio.iscoroutine(result):
                        await result
                except Exception as e:
                    logger.error(f"Error in event handler: {e}")

        except asyncio.QueueFull:
            logger.warning("Status queue full, dropping oldest message")
            try:
                self.event_queue.get_nowait()
                await self.event_queue.put_nowait(msg)
            except asyncio.QueueEmpty:
                pass

    async def get_events_stream(self):
        """Async generator for consuming events"""
        while True:
            try:
                msg = await self.event_queue.get()
                yield msg
            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in event stream: {e}")
                await asyncio.sleep(1)

    def get_active_tasks_summary(self) -> Dict[str, Any]:
        """Get summary of all active tasks"""
        return {
            "active_count": len(self.active_tasks),
            "tasks": self.active_tasks,
            "timestamp": int(datetime.now().timestamp())
        }

    def get_message_history(self, limit: int = 10) -> List[StatusMessage]:
        """Get last N messages from history"""
        return self.message_history[-limit:] if self.message_history else []


async def example_usage():
    """Example usage of the status publisher"""

    def on_event(msg: StatusMessage):
        """Handle event - print to console"""
        print(msg.to_compact_display())
        print()

    # Create publisher
    publisher = LuziaStatusPublisher(event_handler=on_event)
    publisher.set_verbosity("normal")

    # Simulate a task
    task_id = "musica-fix-001"

    await publisher.publish_task_started(
        task_id=task_id,
        project="musica",
        description="Fix audio synthesis engine",
        estimated_duration_seconds=600
    )

    for progress in [25, 50, 75, 100]:
        await asyncio.sleep(1)
        await publisher.publish_progress(
            task_id=task_id,
            progress_percent=progress,
            current_step=progress // 25,
            total_steps=4,
            current_step_name=f"Step {progress // 25}: Testing phase",
            elapsed_seconds=int(600 * progress / 100),
            estimated_remaining_seconds=int(600 * (100 - progress) / 100)
        )

    await publisher.publish_task_completed(
        task_id=task_id,
        elapsed_seconds=615,
        findings_count=2,
        status="APPROVED"
    )


if __name__ == "__main__":
    # Run example
    asyncio.run(example_usage())
