#!/usr/bin/env python3
"""
Event Logger Node - Centralized structured logging of all SaltyBot activities.

Subscribes to /saltybot/* state topics and logs JSON events to JSONL files.
Supports: query service (time/type filter), stats publisher, log rotation,
CSV export, and dashboard live feed.

Event types: encounter, voice_command, trick, e-stop, geofence, dock, error, boot, shutdown
"""

import json
import os
import gzip
import shutil
import csv
import threading
from datetime import datetime, timedelta
from pathlib import Path
from dataclasses import dataclass, asdict
from typing import List, Dict, Any, Optional
from collections import defaultdict
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from rclpy.subscription import Subscription


@dataclass
class Event:
    """Structured event record."""
    timestamp: str  # ISO 8601
    event_type: str  # encounter, voice_command, trick, e-stop, geofence, dock, error, boot, shutdown
    source: str  # Topic that generated event
    data: Dict[str, Any]  # Event-specific data


class EventLogger(Node):
    """
    Centralized event logging node for SaltyBot.

    Subscribes to MQTT state topics and logs structured JSON events.
    Provides query service, stats publishing, log rotation, and export.
    """

    # Event type mapping from topics
    EVENT_TYPES = {
        'first_encounter': 'encounter',
        'voice_command': 'voice_command',
        'trick_state': 'trick',
        'emergency_stop': 'e-stop',
        'geofence': 'geofence',
        'dock_state': 'dock',
        'error': 'error',
        'system_boot': 'boot',
        'system_shutdown': 'shutdown',
    }

    def __init__(self):
        super().__init__('event_logger_node')

        # Configuration
        self.data_dir = Path(os.path.expanduser('~/.saltybot-data/events'))
        self.data_dir.mkdir(parents=True, exist_ok=True)

        self.rotation_days = 7  # Compress files >7 days old
        self.retention_days = 90  # Delete files >90 days old

        # Runtime state
        self.subscriptions: Dict[str, Subscription] = {}
        self.events_lock = threading.Lock()
        self.daily_events: List[Event] = []
        self.daily_stats = defaultdict(int)

        self.get_logger().info(f'Event logger initialized at {self.data_dir}')

        # Subscribe to relevant topics
        self._setup_subscriptions()

        # Timer for stats and rotation
        self.create_timer(60.0, self._publish_stats)  # Stats every 60s
        self.create_timer(3600.0, self._rotate_logs)  # Rotation every hour

        # Publishers
        self.stats_pub = self.create_publisher(String, '/saltybot/event_stats', 10)
        self.live_feed_pub = self.create_publisher(String, '/saltybot/event_feed', 10)

    def _setup_subscriptions(self):
        """Subscribe to saltybot/* topics for event detection."""
        # Subscribe to specific event topics
        event_topics = [
            '/saltybot/first_encounter',
            '/saltybot/voice_command',
            '/saltybot/trick_state',
            '/saltybot/emergency_stop',
            '/saltybot/geofence',
            '/saltybot/dock_state',
            '/saltybot/error',
            '/saltybot/system_boot',
            '/saltybot/system_shutdown',
        ]

        for topic in event_topics:
            # Extract event type from topic
            topic_name = topic.split('/')[-1]
            event_type = self.EVENT_TYPES.get(topic_name, topic_name)

            self.subscriptions[topic] = self.create_subscription(
                String,
                topic,
                lambda msg, et=event_type, t=topic: self._on_event(msg, et, t),
                10
            )

    def _on_event(self, msg: String, event_type: str, topic: str):
        """Handle incoming event message."""
        try:
            # Parse message as JSON if possible, otherwise use as string
            try:
                data = json.loads(msg.data)
            except (json.JSONDecodeError, AttributeError):
                data = {'message': str(msg.data)}

            event = Event(
                timestamp=datetime.now().isoformat(),
                event_type=event_type,
                source=topic,
                data=data
            )

            self._log_event(event)
            self._publish_to_feed(event)

        except Exception as e:
            self.get_logger().error(f'Error processing event: {e}')

    def _log_event(self, event: Event):
        """Write event to daily JSONL file."""
        try:
            with self.events_lock:
                # Daily filename
                today = datetime.now().strftime('%Y-%m-%d')
                log_file = self.data_dir / f'{today}.jsonl'

                # Append to file
                with open(log_file, 'a') as f:
                    f.write(json.dumps(asdict(event)) + '\n')

                # Update in-memory stats
                self.daily_events.append(event)
                self.daily_stats[event.event_type] += 1
                if 'person_id' in event.data:
                    self.daily_stats['encounters'] += 1
                if event.event_type == 'error':
                    self.daily_stats['errors'] += 1

        except Exception as e:
            self.get_logger().error(f'Error logging event: {e}')

    def _publish_to_feed(self, event: Event):
        """Publish event to live feed topic."""
        try:
            feed_msg = String()
            feed_msg.data = json.dumps(asdict(event))
            self.live_feed_pub.publish(feed_msg)
        except Exception as e:
            self.get_logger().error(f'Error publishing to feed: {e}')

    def _publish_stats(self):
        """Publish daily statistics."""
        try:
            stats = {
                'timestamp': datetime.now().isoformat(),
                'total_events': len(self.daily_events),
                'by_type': dict(self.daily_stats),
                'encounters': self.daily_stats.get('encounters', 0),
                'errors': self.daily_stats.get('errors', 0),
            }

            msg = String()
            msg.data = json.dumps(stats)
            self.stats_pub.publish(msg)

        except Exception as e:
            self.get_logger().error(f'Error publishing stats: {e}')

    def _rotate_logs(self):
        """Handle log rotation: compress >7 days, delete >90 days."""
        try:
            now = datetime.now()

            for log_file in sorted(self.data_dir.glob('*.jsonl')):
                # Extract date from filename (YYYY-MM-DD.jsonl)
                try:
                    date_str = log_file.stem
                    file_date = datetime.strptime(date_str, '%Y-%m-%d')
                    age_days = (now - file_date).days

                    if age_days > self.retention_days:
                        # Delete very old files
                        log_file.unlink()
                        self.get_logger().info(f'Deleted {log_file.name} (age: {age_days}d)')

                    elif age_days > self.rotation_days:
                        # Compress old files
                        gz_file = log_file.with_suffix('.jsonl.gz')
                        if not gz_file.exists():
                            with open(log_file, 'rb') as f_in:
                                with gzip.open(gz_file, 'wb') as f_out:
                                    shutil.copyfileobj(f_in, f_out)
                            log_file.unlink()
                            self.get_logger().info(f'Compressed {log_file.name}')

                except ValueError:
                    # Skip files with unexpected naming
                    pass

        except Exception as e:
            self.get_logger().error(f'Error rotating logs: {e}')

    def query_events(self, event_types: Optional[List[str]] = None,
                    start_time: Optional[str] = None,
                    end_time: Optional[str] = None) -> List[Event]:
        """
        Query events by type and time range.

        Args:
            event_types: List of event types to filter (None = all)
            start_time: ISO 8601 start timestamp (None = all)
            end_time: ISO 8601 end timestamp (None = all)

        Returns:
            List of matching events
        """
        results = []

        try:
            # Determine date range to search
            if start_time:
                start = datetime.fromisoformat(start_time)
            else:
                start = datetime.now() - timedelta(days=90)

            if end_time:
                end = datetime.fromisoformat(end_time)
            else:
                end = datetime.now()

            # Search JSONL files
            for log_file in sorted(self.data_dir.glob('*.jsonl')):
                try:
                    file_date_str = log_file.stem
                    file_date = datetime.strptime(file_date_str, '%Y-%m-%d')

                    # Skip files outside time range
                    if file_date < start or file_date > end:
                        continue

                    with open(log_file, 'r') as f:
                        for line in f:
                            if not line.strip():
                                continue

                            event_dict = json.loads(line)
                            event = Event(**event_dict)

                            # Check timestamp in range
                            event_time = datetime.fromisoformat(event.timestamp)
                            if event_time < start or event_time > end:
                                continue

                            # Check type filter
                            if event_types and event.event_type not in event_types:
                                continue

                            results.append(event)

                except (ValueError, json.JSONDecodeError):
                    pass

        except Exception as e:
            self.get_logger().error(f'Error querying events: {e}')

        return results

    def export_csv(self, filename: Optional[str] = None) -> str:
        """
        Export all events to CSV.

        Args:
            filename: Output filename (default: events_YYYY-MM-DD.csv)

        Returns:
            Path to exported file
        """
        if not filename:
            today = datetime.now().strftime('%Y-%m-%d')
            filename = f'events_{today}.csv'

        output_path = self.data_dir / filename

        try:
            with self.events_lock:
                events = self.query_events()

                with open(output_path, 'w', newline='') as f:
                    writer = csv.writer(f)
                    writer.writerow(['Timestamp', 'Type', 'Source', 'Data'])

                    for event in events:
                        writer.writerow([
                            event.timestamp,
                            event.event_type,
                            event.source,
                            json.dumps(event.data)
                        ])

                self.get_logger().info(f'Exported {len(events)} events to {output_path}')
                return str(output_path)

        except Exception as e:
            self.get_logger().error(f'Error exporting CSV: {e}')
            return ''

    def get_daily_stats(self) -> Dict[str, Any]:
        """Get stats for current day."""
        return {
            'timestamp': datetime.now().isoformat(),
            'total_events': len(self.daily_events),
            'by_type': dict(self.daily_stats),
            'encounters': self.daily_stats.get('encounters', 0),
            'errors': self.daily_stats.get('errors', 0),
        }


def main(args=None):
    rclpy.init(args=args)
    node = EventLogger()

    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()
