"""
System Events - Background task event queue

Handles:
- Event emission and subscription
- Event persistence for recovery
- Priority-based processing
- Event history
"""

import asyncio
import json
import os
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional

from navig.debug_logger import get_debug_logger

if TYPE_CHECKING:
    pass

logger = get_debug_logger()


class EventPriority(Enum):
    """Event priority levels."""
    LOW = 0
    NORMAL = 1
    HIGH = 2
    CRITICAL = 3


@dataclass
class SystemEvent:
    """A system event."""
    id: str
    event_type: str
    payload: Dict[str, Any]
    priority: EventPriority
    timestamp: datetime
    processed: bool = False
    error: Optional[str] = None

    def to_dict(self) -> dict:
        return {
            'id': self.id,
            'event_type': self.event_type,
            'payload': self.payload,
            'priority': self.priority.name,
            'timestamp': self.timestamp.isoformat(),
            'processed': self.processed,
            'error': self.error,
        }

    @classmethod
    def from_dict(cls, data: dict) -> 'SystemEvent':
        return cls(
            id=data['id'],
            event_type=data['event_type'],
            payload=data['payload'],
            priority=EventPriority[data['priority']],
            timestamp=datetime.fromisoformat(data['timestamp']),
            processed=data.get('processed', False),
            error=data.get('error'),
        )


# Event handler type
EventHandler = Callable[[SystemEvent], Any]


class SystemEventQueue:
    """
    Persistent event queue for background tasks.
    
    Features:
    - Priority-based processing
    - Event persistence to disk
    - Subscription model
    - Event history
    """

    def __init__(
        self,
        storage_path: Path,
        max_history: int = 1000
    ):
        self.storage_path = storage_path
        self.max_history = max_history

        # Event queue (priority queue simulation)
        self._queue: asyncio.Queue = asyncio.Queue()

        # Pending events (not yet processed)
        self._pending: Dict[str, SystemEvent] = {}

        # Event history
        self._history: List[SystemEvent] = []

        # Subscribers by event type
        self._subscribers: Dict[str, List[EventHandler]] = {}

        # Wildcard subscribers (receive all events)
        self._wildcard_subscribers: List[EventHandler] = []

        # Running state
        self._running = False
        self._processor_task: Optional[asyncio.Task] = None

        # Event counter for ID generation
        self._event_counter = 0

        # Load persisted events
        self._load_events()

    def _get_events_path(self) -> Path:
        return self.storage_path / 'events.json'

    def _load_events(self) -> None:
        """Load pending events from disk."""
        events_path = self._get_events_path()

        if events_path.exists():
            try:
                data = json.loads(events_path.read_text())

                # Load pending events
                for event_data in data.get('pending', []):
                    event = SystemEvent.from_dict(event_data)
                    self._pending[event.id] = event

                # Load counter
                self._event_counter = data.get('counter', 0)

                logger.info(f"Loaded {len(self._pending)} pending events")

            except Exception as e:
                logger.error(f"Failed to load events: {e}")

    def _save_events(self) -> None:
        """Save pending events to disk."""
        self.storage_path.mkdir(parents=True, exist_ok=True)

        data = {
            'counter': self._event_counter,
            'pending': [e.to_dict() for e in self._pending.values()],
        }

        self._get_events_path().write_text(json.dumps(data, indent=2))

    def _generate_id(self) -> str:
        """Generate unique event ID."""
        self._event_counter += 1
        return f"evt_{self._event_counter}_{datetime.now().strftime('%Y%m%d%H%M%S')}"

    async def start(self) -> None:
        """Start event processor."""
        if self._running:
            return

        self._running = True

        # Queue pending events for processing
        for event in sorted(
            self._pending.values(),
            key=lambda e: (e.priority.value, e.timestamp),
            reverse=True  # Higher priority first
        ):
            await self._queue.put(event)

        # Start processor
        self._processor_task = asyncio.create_task(self._process_loop())

        logger.info("Event queue started")

    async def stop(self) -> None:
        """Stop event processor."""
        self._running = False

        if self._processor_task:
            self._processor_task.cancel()
            try:
                await self._processor_task
            except asyncio.CancelledError:
                pass  # task cancelled; expected during shutdown

        # Save pending events
        self._save_events()

        logger.info("Event queue stopped")

    async def emit(
        self,
        event_type: str,
        payload: Optional[Dict[str, Any]] = None,
        priority: EventPriority = EventPriority.NORMAL
    ) -> str:
        """
        Emit an event.
        
        Args:
            event_type: Type of event (e.g., 'host_check', 'config_reloaded')
            payload: Event data
            priority: Processing priority
            
        Returns:
            Event ID
        """
        event = SystemEvent(
            id=self._generate_id(),
            event_type=event_type,
            payload=payload or {},
            priority=priority,
            timestamp=datetime.now(),
        )

        # Add to pending
        self._pending[event.id] = event
        self._save_events()

        # Queue for processing
        await self._queue.put(event)

        logger.debug(f"Event emitted: {event_type} (id={event.id})")

        return event.id

    def subscribe(
        self,
        event_type: str,
        handler: EventHandler
    ) -> None:
        """
        Subscribe to events of a specific type.
        
        Args:
            event_type: Event type to subscribe to, or '*' for all
            handler: Callback function
        """
        if event_type == '*':
            self._wildcard_subscribers.append(handler)
        else:
            if event_type not in self._subscribers:
                self._subscribers[event_type] = []
            self._subscribers[event_type].append(handler)

        logger.debug(f"Subscribed to event: {event_type}")

    def unsubscribe(
        self,
        event_type: str,
        handler: EventHandler
    ) -> None:
        """Unsubscribe from events."""
        if event_type == '*':
            if handler in self._wildcard_subscribers:
                self._wildcard_subscribers.remove(handler)
        else:
            if event_type in self._subscribers:
                if handler in self._subscribers[event_type]:
                    self._subscribers[event_type].remove(handler)

    async def _process_loop(self) -> None:
        """Main event processing loop."""
        while self._running:
            try:
                # Get event with timeout
                try:
                    event = await asyncio.wait_for(
                        self._queue.get(),
                        timeout=1.0
                    )
                except asyncio.TimeoutError:
                    continue

                await self._process_event(event)

            except asyncio.CancelledError:
                break
            except Exception as e:
                logger.error(f"Error in event processor: {e}")

    async def _process_event(self, event: SystemEvent) -> None:
        """Process a single event."""
        logger.debug(f"Processing event: {event.event_type} (id={event.id})")

        handlers = []

        # Get type-specific handlers
        if event.event_type in self._subscribers:
            handlers.extend(self._subscribers[event.event_type])

        # Add wildcard handlers
        handlers.extend(self._wildcard_subscribers)

        # Call all handlers
        errors = []
        for handler in handlers:
            try:
                if asyncio.iscoroutinefunction(handler):
                    await handler(event)
                else:
                    handler(event)
            except Exception as e:
                errors.append(str(e))
                logger.error(f"Handler error for {event.event_type}: {e}")

        # Mark as processed
        event.processed = True
        if errors:
            event.error = "; ".join(errors)

        # Move to history
        if event.id in self._pending:
            del self._pending[event.id]

        self._history.append(event)

        # Trim history
        if len(self._history) > self.max_history:
            self._history = self._history[-self.max_history:]

        self._save_events()

    def get_pending(self) -> List[SystemEvent]:
        """Get all pending events."""
        return list(self._pending.values())

    def get_history(
        self,
        event_type: Optional[str] = None,
        limit: int = 50
    ) -> List[SystemEvent]:
        """Get event history."""
        events = self._history

        if event_type:
            events = [e for e in events if e.event_type == event_type]

        return events[-limit:]


# Common event types
class EventTypes:
    """Standard event type constants."""

    # Host events
    HOST_CHECK = 'host_check'
    HOST_DOWN = 'host_down'
    HOST_UP = 'host_up'
    HOST_DISK_WARNING = 'host_disk_warning'
    HOST_MEMORY_WARNING = 'host_memory_warning'

    # Certificate events
    CERT_EXPIRY_WARNING = 'cert_expiry_warning'
    CERT_EXPIRED = 'cert_expired'

    # Service events
    SERVICE_DOWN = 'service_down'
    SERVICE_UP = 'service_up'
    SERVICE_RESTARTED = 'service_restarted'

    # Configuration events
    CONFIG_RELOADED = 'config_reloaded'
    CONFIG_ERROR = 'config_error'

    # Session events
    SESSION_CREATED = 'session_created'
    SESSION_EXPIRED = 'session_expired'

    # Heartbeat events
    HEARTBEAT_START = 'heartbeat_start'
    HEARTBEAT_COMPLETE = 'heartbeat_complete'
    HEARTBEAT_FAILED = 'heartbeat_failed'

    # Cron events
    CRON_JOB_START = 'cron_job_start'
    CRON_JOB_COMPLETE = 'cron_job_complete'
    CRON_JOB_FAILED = 'cron_job_failed'

    # Notification events
    NOTIFICATION_SENT = 'notification_sent'
    NOTIFICATION_SUPPRESSED = 'notification_suppressed'

    # Workspace events
    WORKSPACE_FILE_CHANGED = 'workspace_file_changed'


class SmartNotificationFilter:
    """
    Filters notifications based on context and patterns.
    
    Features:
    - HEARTBEAT_OK suppression
    - Duplicate suppression
    - Rate limiting
    - Priority-based delivery
    """

    def __init__(
        self,
        event_queue: SystemEventQueue,
        cooldown_seconds: int = 300,  # 5 minutes
        quiet_hours_enabled: bool = False,
        quiet_hours_start: int = 23,
        quiet_hours_end: int = 7,
        notifications_enabled: bool = True,
    ):
        self.event_queue = event_queue
        self.cooldown_seconds = cooldown_seconds
        self.quiet_hours_enabled = bool(
            quiet_hours_enabled if quiet_hours_enabled is not None else _env_bool("NAVIG_QUIET_HOURS_ENABLED", False)
        )
        self.quiet_hours_start = int(os.getenv("NAVIG_QUIET_HOURS_START", quiet_hours_start))
        self.quiet_hours_end = int(os.getenv("NAVIG_QUIET_HOURS_END", quiet_hours_end))
        self.notifications_enabled = bool(
            notifications_enabled if notifications_enabled is not None else _env_bool("NAVIG_NOTIFICATIONS_ENABLED", True)
        )

        # Track recent notifications for dedup
        self._recent: Dict[str, datetime] = {}

        # Subscribe to notification events
        event_queue.subscribe(
            EventTypes.NOTIFICATION_SENT,
            self._on_notification
        )

    async def should_notify(
        self,
        notification_type: str,
        message: str,
        priority: EventPriority = EventPriority.NORMAL
    ) -> bool:
        """
        Determine if a notification should be sent.
        
        Returns False for:
        - HEARTBEAT_OK messages
        - Duplicate messages within cooldown
        - Low priority during quiet hours
        """
        if not self.notifications_enabled:
            await self.event_queue.emit(
                EventTypes.NOTIFICATION_SUPPRESSED,
                {'reason': 'notifications_disabled', 'type': notification_type}
            )
            return False

        # Check for HEARTBEAT_OK
        if 'HEARTBEAT_OK' in message:
            logger.debug("Suppressing HEARTBEAT_OK notification")
            await self.event_queue.emit(
                EventTypes.NOTIFICATION_SUPPRESSED,
                {'reason': 'heartbeat_ok', 'type': notification_type}
            )
            return False

        # Check for duplicates
        cache_key = f"{notification_type}:{hash(message)}"
        now = datetime.now()

        if cache_key in self._recent:
            last_sent = self._recent[cache_key]
            if (now - last_sent).total_seconds() < self.cooldown_seconds:
                logger.debug(f"Suppressing duplicate notification: {notification_type}")
                await self.event_queue.emit(
                    EventTypes.NOTIFICATION_SUPPRESSED,
                    {'reason': 'duplicate', 'type': notification_type}
                )
                return False

        # Critical priority always goes through
        if priority == EventPriority.CRITICAL:
            return True

        # Quiet hours suppression for non-urgent traffic
        if self.quiet_hours_enabled and self._is_quiet_hours():
            if priority in (EventPriority.LOW, EventPriority.NORMAL):
                await self.event_queue.emit(
                    EventTypes.NOTIFICATION_SUPPRESSED,
                    {'reason': 'quiet_hours', 'type': notification_type}
                )
                return False

        return True

    async def notify(
        self,
        notification_type: str,
        message: str,
        channel: str,
        recipient: str,
        priority: EventPriority = EventPriority.NORMAL
    ) -> bool:
        """
        Send a notification if it passes filters.
        
        Returns True if notification was sent.
        """
        if not await self.should_notify(notification_type, message, priority):
            return False

        # Record notification
        cache_key = f"{notification_type}:{hash(message)}"
        self._recent[cache_key] = datetime.now()

        # Emit event
        await self.event_queue.emit(
            EventTypes.NOTIFICATION_SENT,
            {
                'type': notification_type,
                'channel': channel,
                'recipient': recipient,
                'message_length': len(message),
            }
        )

        return True

    def _on_notification(self, event: SystemEvent) -> None:
        """Track sent notifications."""
        # Cleanup old entries
        now = datetime.now()
        old_keys = [
            k for k, v in self._recent.items()
            if (now - v).total_seconds() > self.cooldown_seconds * 2
        ]
        for k in old_keys:
            del self._recent[k]

    def _is_quiet_hours(self) -> bool:
        now_hour = datetime.now().hour
        start = self.quiet_hours_start % 24
        end = self.quiet_hours_end % 24
        if start == end:
            return False
        # Wrap-around window (e.g. 23 -> 7)
        if start > end:
            return now_hour >= start or now_hour < end
        return start <= now_hour < end


def _env_bool(name: str, default: bool) -> bool:
    raw = os.getenv(name)
    if raw is None:
        return default
    return raw.strip().lower() in {"1", "true", "yes", "on"}
