import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:sqlite3/sqlite3.dart';
import 'package:xp_nix/src/models/activity_event.dart';
import 'package:xp_nix/src/testing/mock_idle_monitor.dart';
import '../interfaces/i_idle_monitor.dart';
import '../interfaces/i_activity_detector.dart';
import '../interfaces/i_time_provider.dart';
import '../interfaces/i_desktop_enhancer.dart';
import '../interfaces/i_window_manager.dart';
import '../detectors/zoom_detector.dart';
import '../database/database_manager.dart';
import '../config/config_manager.dart';
import '../logging/logger.dart';
import '../notifications/xp_notification_manager.dart';
import '../web/websocket_manager.dart';
import '../factories/window_manager_factory.dart';
import '../detectors/window_manager_detector.dart';
import '../overlays/cursor_overlay_manager.dart';

/// Unified ProductivityMonitor with dependency injection for both production and testing
class ProductivityMonitor {
  late final DatabaseManager _dbManager;
  ConfigManager get _configManager => ConfigManager.instance;
  late final IIdleMonitor _idleMonitor;
  late final IActivityDetector? _activityDetector;
  late final ITimeProvider _timeProvider;
  late final IDesktopEnhancer _desktopEnhancer;
  late final XPNotificationManager _xpNotificationManager;
  late final ZoomDetector? _zoomDetector;
  late final IWindowManager? _windowManager;
  late final CursorOverlayManager? _cursorOverlayManager;

  Timer? _pollTimer;
  String? _lastActiveWindow;
  String? _lastActiveWindowTitle;
  DateTime? _lastActivityTime;
  DateTime? _lastActiveTime;
  ZoomStatus _lastZoomStatus = ZoomStatus.none;
  DateTime? _lastZoomStatusTime;
  int _lastKnownLevel = 1;
  final int _pollFrequencySeconds = 5;
  // Only save activities longer than cutoff
  final int _activityDurationCutoffSeconds = 10;

  StreamSubscription? _idleSubscription;
  StreamSubscription? _activitySubscription;

  ProductivityMonitor({
    required Database db,
    required IIdleMonitor idleMonitor,
    required ITimeProvider timeProvider,
    required IDesktopEnhancer desktopEnhancer,
    IActivityDetector? activityDetector,
  }) {
    _dbManager = DatabaseManager(db);
    _idleMonitor = idleMonitor;
    _activityDetector = activityDetector;
    _timeProvider = timeProvider;
    _desktopEnhancer = desktopEnhancer;
    _xpNotificationManager = XPNotificationManager(_dbManager);

    // Initialize window manager for legacy polling mode
    _windowManager = WindowManagerFactory.create();

    // Initialize cursor overlay manager
    if (_windowManager != null) {
      _cursorOverlayManager = CursorOverlayManager(
        windowManager: _windowManager!,
        enabled: ConfigManager.instance.isOverlayEnabled(),
      );
      
      // Log overlay availability on startup
      _cursorOverlayManager!.isAvailable().then((available) {
        if (available) {
          Logger.info('XP cursor overlay system initialized successfully');
        } else {
          Logger.warn('XP cursor overlay system is not available');
        }
      });
    } else {
      _cursorOverlayManager = null;
      Logger.warn('Window manager not available, cursor overlay disabled');
    }

    // Initialize zoom detector (only if not in test mode)
    if (_activityDetector == null) {
      _zoomDetector = ZoomDetector();
    } else {
      _zoomDetector = null;
    }
  }

  void start() {
    _dbManager.initDatabase();

    // Initialize XP notification manager
    _xpNotificationManager.start();

    // Start idle monitor
    _idleMonitor.start();

    // Listen to idle state changes
    _idleSubscription = _idleMonitor.idleStateStream.listen((idleStatus) {
      _handleIdleStateChange(idleStatus);
    });

    // Listen to activity changes if activity detector is provided
    if (_activityDetector != null) {
      _activityDetector.start();
      _activitySubscription = _activityDetector.activityStream.listen((event) {
        _handleActivityEvent(event);
      });
    }

    // Check for level changes and apply visual upgrades
    _timeProvider.periodic(Duration(minutes: 1), (_) => _checkForLevelUp());

    // Apply initial theme based on current level
    _applyCurrentLevelTheme();

    // Start polling for activity if no activity detector provided (legacy mode)
    if (_activityDetector == null) {
      _pollTimer = _timeProvider.periodic(Duration(seconds: _pollFrequencySeconds), (_) => _pollActivity());
      print('🚀 Started Enhanced Hyprland monitoring with desktop gamification...');
    } else {
      print('🧪 Started productivity monitor with injected dependencies...');
    }
  }

  void stop() {
    _pollTimer?.cancel();
    _idleSubscription?.cancel();
    _activitySubscription?.cancel();
    _idleMonitor.stop();
    _activityDetector?.stop();
    _xpNotificationManager.stop();
  }

  /// Handle idle state changes from the idle monitor
  void _handleIdleStateChange(IdleStatus idleStatus) {
    print('DEBUG: Idle state changed to: $idleStatus');

    // When user goes deep idle, end the current activity and award XP
    if (idleStatus == IdleStatus.deepIdle) {
      print('😴 User went deep idle - ending current activity');
      _endCurrentActivityOnIdle();
    }
    // Note: We don't need to handle lightIdle or active states here
    // as they don't require ending the current activity
  }

  /// End the current activity when user goes deep idle
  void _endCurrentActivityOnIdle() {
    if (_lastActiveWindow != null && _lastActivityTime != null) {
      final duration = _timeProvider.now().difference(_lastActivityTime!).inSeconds;
      print('DEBUG: Ending activity $_lastActiveWindow due to deep idle with duration ${duration}s');

      // Save the activity if it meets the minimum duration requirement
      if (duration >= _activityDurationCutoffSeconds) {
        _saveActivityEvent(_lastActiveWindow!, duration, _lastActiveWindowTitle ?? '');
        print('💾 Saved activity due to deep idle: $_lastActiveWindow (${duration}s)');
      } else {
        print('DEBUG: Activity duration too short ($duration < $_activityDurationCutoffSeconds), not saving');
      }

      // Clear the current activity state
      _lastActiveWindow = null;
      _lastActiveWindowTitle = null;
      _lastActivityTime = null;
    } else {
      print('DEBUG: No current activity to end on deep idle');
    }
  }

  /// Handle activity events from the activity detector
  void _handleActivityEvent(ActivityEvent event) {
    final now = _timeProvider.now();

    print('DEBUG: Handling activity event: ${event.application} - ${event.type}');
    print('DEBUG: Current state - window: $_lastActiveWindow, time: $_lastActivityTime');

    // If this is a different activity from the last one, save the previous activity
    if (_lastActiveWindow != null &&
        (_lastActiveWindow != event.application || _lastActiveWindowTitle != event.metadata)) {
      final duration = now.difference(_lastActivityTime ?? now).inSeconds;
      print('DEBUG: Saving previous activity $_lastActiveWindow with duration ${duration}s');
      if (duration >= 10) {
        _saveActivityEvent(_lastActiveWindow!, duration, _lastActiveWindowTitle ?? '');
      }
    }

    _lastActiveWindow = event.application;
    _lastActiveWindowTitle = event.metadata ?? '';
    _lastActivityTime = now;

    print('DEBUG: Updated state - window: $_lastActiveWindow, time: $_lastActivityTime');
    print('Activity: ${event.application} - ${event.type}');
  }

  /// Force save current activity (useful for testing)
  void flushCurrentActivity() {
    if (_lastActiveWindow != null && _lastActivityTime != null) {
      final duration = _timeProvider.now().difference(_lastActivityTime!).inSeconds;
      print('DEBUG: Flushing activity $_lastActiveWindow with duration ${duration}s');
      if (duration >= _activityDurationCutoffSeconds) {
        _saveActivityEvent(_lastActiveWindow!, duration, _lastActiveWindowTitle ?? '');
        _lastActiveWindow = null;
        _lastActiveWindowTitle = null;
        _lastActivityTime = null;
      } else {
        print('DEBUG: Duration too short ($duration < $_activityDurationCutoffSeconds), not saving');
      }
    } else {
      print('DEBUG: No activity to flush (window: $_lastActiveWindow, time: $_lastActivityTime)');
    }
  }

  /// Force save current activity with minimum duration (useful for testing)
  void flushCurrentActivityForced() {
    if (_lastActiveWindow != null && _lastActivityTime != null) {
      final duration = _timeProvider.now().difference(_lastActivityTime!).inSeconds;
      print('DEBUG: Force flushing activity $_lastActiveWindow with duration ${duration}s');
      _saveActivityEvent(_lastActiveWindow!, duration, _lastActiveWindowTitle ?? '');
      _lastActiveWindow = null;
      _lastActiveWindowTitle = null;
      _lastActivityTime = null;
    } else {
      print('DEBUG: No activity to force flush (window: $_lastActiveWindow, time: $_lastActivityTime)');
    }
  }

  Future<void> _checkForLevelUp() async {
    final stats = _dbManager.getTodayStats();
    final currentLevel = stats['level'] as int;

    if (currentLevel > _lastKnownLevel) {
      print('🎉 LEVEL UP DETECTED! $_lastKnownLevel → $currentLevel');

      // Celebrate the level up with desktop enhancement
      await _desktopEnhancer.celebrateLevelUp(currentLevel);

      // Record the theme change
      _recordThemeChange(currentLevel);

      // Check for achievements
      await _checkAchievements(currentLevel, stats);

      // Update timestamp for level up
      final today = _timeProvider.now().toIso8601String().substring(0, 10);
      _dbManager.updateLevelUpTimestamp(today, _timeProvider.now().millisecondsSinceEpoch);

      _lastKnownLevel = currentLevel;

      // Show detailed level up message
      _showLevelUpMessage(currentLevel, stats);

      // Send level up notification
      await _xpNotificationManager.showLevelUp(newLevel: currentLevel, totalXP: stats['xp'] as int, stats: stats);

      // Broadcast level up via WebSocket
      _broadcastLevelUp(currentLevel);
    }
  }

  void _recordThemeChange(int level) {
    final themeName = _desktopEnhancer.getCurrentThemeInfo();
    _dbManager.recordThemeChange(level, themeName, _timeProvider.now().millisecondsSinceEpoch);
  }

  Future<void> _checkAchievements(int level, Map<String, dynamic> stats) async {
    final achievements = <Map<String, dynamic>>[];

    // Level-based achievements
    if (level == 5) {
      achievements.add({
        'name': 'Rising Star',
        'description': 'Reached level 5 - Your journey begins to shine!',
        'xp_reward': 100,
      });
    } else if (level == 10) {
      achievements.add({
        'name': 'Productivity Warrior',
        'description': 'Reached level 10 - You\'ve unlocked desktop blur effects!',
        'xp_reward': 250,
      });
    } else if (level == 15) {
      achievements.add({
        'name': 'Focus Master',
        'description': 'Reached level 15 - Your desktop now glows with productivity!',
        'xp_reward': 500,
      });
    } else if (level == 25) {
      achievements.add({
        'name': 'Legendary Achiever',
        'description': 'Reached level 25 - You have transcended ordinary productivity!',
        'xp_reward': 1000,
      });
    }

    // Focus-based achievements
    final focusHours = (stats['focus_time'] as int) / 3600;
    if (focusHours >= 4 && !_dbManager.hasAchievement('Deep Focus')) {
      achievements.add({
        'name': 'Deep Focus',
        'description': 'Maintained 4+ hours of focus time in a day',
        'xp_reward': 200,
      });
    }

    if (focusHours >= 8 && !_dbManager.hasAchievement('Focus Titan')) {
      achievements.add({
        'name': 'Focus Titan',
        'description': 'Achieved 8+ hours of pure focus - Incredible!',
        'xp_reward': 500,
      });
    }

    // Session-based achievements
    final focusSessions = stats['focus_sessions'] as int;
    if (focusSessions >= 5 && !_dbManager.hasAchievement('Session Master')) {
      achievements.add({
        'name': 'Session Master',
        'description': 'Completed 5+ focus sessions in one day',
        'xp_reward': 150,
      });
    }

    // Meeting achievements
    final meetingHours = (stats['meeting_time'] as int) / 3600;
    if (meetingHours >= 3 && !_dbManager.hasAchievement('Communication Pro')) {
      achievements.add({
        'name': 'Communication Pro',
        'description': 'Participated in 3+ hours of meetings',
        'xp_reward': 100,
      });
    }

    // Award achievements
    for (final achievement in achievements) {
      await _awardAchievement(achievement, level);
    }
  }

  Future<void> _awardAchievement(Map<String, dynamic> achievement, int level) async {
    _dbManager.saveAchievement(
      achievement['name'],
      achievement['description'],
      achievement['xp_reward'],
      _timeProvider.now().millisecondsSinceEpoch,
      level,
    );

    // Add bonus XP
    _dbManager.updateDailyStats(achievement['xp_reward'] as int, 0, 0);

    // Show cursor overlay for achievement bonus XP
    _cursorOverlayManager?.showXPNumber(achievement['xp_reward'] as int);

    print('🏆 ACHIEVEMENT UNLOCKED: ${achievement['name']}');
    print('   ${achievement['description']}');
    print('   Bonus: +${achievement['xp_reward']} XP');

    // Send achievement notification using notification manager
    await _xpNotificationManager.showAchievement(
      name: achievement['name'],
      description: achievement['description'],
      xpReward: achievement['xp_reward'],
      currentLevel: level,
    );

    // Broadcast achievement via WebSocket
    _broadcastAchievementUnlocked(achievement);
  }

  void _showLevelUpMessage(int level, Map<String, dynamic> stats) {
    final focusHours = ((stats['focus_time'] as int) / 3600).toStringAsFixed(1);
    final meetingHours = ((stats['meeting_time'] as int) / 3600).toStringAsFixed(1);

    print('\n${'=' * 50}');
    print('🎮 LEVEL UP! Welcome to Level $level! 🎮');
    print('=' * 50);
    print('⭐ Total XP: ${stats['xp']}');
    print('🧠 Focus Time: ${focusHours}h');
    print('🤝 Meeting Time: ${meetingHours}h');
    print('🎯 Focus Sessions: ${stats['focus_sessions']}');
    print('🎨 Desktop Theme: ${_desktopEnhancer.getCurrentThemeInfo()}');
    print('=' * 50);
    print('');
  }

  Future<void> _applyCurrentLevelTheme() async {
    final stats = _dbManager.getTodayStats();
    final currentLevel = stats['level'] as int;
    _lastKnownLevel = currentLevel;

    await _desktopEnhancer.applyThemeForLevel(currentLevel);
    print('🎨 Applied theme for current level: $currentLevel');
  }

  // Enhanced XP calculation with multipliers
  int _calculateXP(ActivityEventType activityType, int durationSeconds) {
    final minutes = (durationSeconds / 60).round();
    final baseXPPerMinute = _getBaseXPForType(activityType);
    final baseXP = minutes * baseXPPerMinute;
    final timeMultiplier = _configManager.getTimeMultiplier(_timeProvider.now());
    final finalXP = (baseXP * timeMultiplier).round();

    Logger.logXPCalculation(activityType, baseXP, timeMultiplier, finalXP);
    return finalXP;
  }

  int _getBaseXPForType(ActivityEventType category) {
    // Use exhaustive matching with ActivityEventType enum for consistent XP rewards
    switch (category) {
      case ActivityEventType.coding:
        return 10;
      case ActivityEventType.focusedBrowsing:
        return 6;
      case ActivityEventType.collaboration:
        return 7;
      case ActivityEventType.meetings:
        return 3;
      case ActivityEventType.misc:
        return 2;
      case ActivityEventType.uncategorized:
        return 1;
    }
  }

  // Enhanced focus session rewards
  void _awardFocusSessionXP(int focusMinutes) {
    final bonusXP = _configManager.getFocusSessionBonus(focusMinutes);
    final today = _timeProvider.now().toIso8601String().substring(0, 10);

    _dbManager.saveFocusSession(today, focusMinutes, bonusXP, _timeProvider.now().millisecondsSinceEpoch);
    _dbManager.updateDailyStats(bonusXP, 0, 0);

    // Show cursor overlay for focus session bonus XP
    _cursorOverlayManager?.showXPNumber(bonusXP);

    String message = '🎯 Focus session complete: +$bonusXP XP for $focusMinutes min!';
    if (focusMinutes >= 180) {
      message = '🔥 LEGENDARY FOCUS! +$bonusXP XP for $focusMinutes min! 🔥';
    } else if (focusMinutes >= 120) {
      message = '⚡ EPIC FOCUS! +$bonusXP XP for $focusMinutes min! ⚡';
    } else if (focusMinutes >= 60) {
      message = '💪 POWER FOCUS! +$bonusXP XP for $focusMinutes min! 💪';
    }

    Logger.logFocusSession(focusMinutes, bonusXP);
    print(message);

    // Send focus session notification
    _xpNotificationManager.showFocusSession(durationMinutes: focusMinutes, bonusXP: bonusXP, sessionType: 'focus');

    // Broadcast focus session completion via WebSocket
    _broadcastFocusSessionComplete(focusMinutes, bonusXP);

    // Broadcast updated stats and XP breakdown
    _broadcastStatsUpdate();
    _broadcastXPBreakdownUpdate();
  }

  // Enhanced stats display
  void printDetailedStats() {
    final stats = _dbManager.getTodayStats();
    final streaks = _dbManager.getStreakStats();
    final focusHours = (stats['focus_time'] / 3600).toStringAsFixed(1);
    final meetingHours = (stats['meeting_time'] / 3600).toStringAsFixed(1);

    print('\n${'=' * 60}');
    print('🎮 PRODUCTIVITY DASHBOARD - Level ${stats['level']} 🎮');
    print('=' * 60);
    print('⭐ XP: ${stats['xp']} | 🔥 Streak: ${streaks['current_streak']} days');
    print('🧠 Focus: ${focusHours}h | 🤝 Meetings: ${meetingHours}h');
    print('🎯 Sessions: ${stats['focus_sessions']} | 📈 Best Streak: ${streaks['longest_streak']}');
    print('🎨 Theme: ${_desktopEnhancer.getCurrentThemeInfo()}');

    // Show recent achievements
    final recentAchievements = _dbManager.getRecentAchievements();

    if (recentAchievements.isNotEmpty) {
      print('🏆 Today\'s Achievements:');
      for (final achievement in recentAchievements) {
        print('   • ${achievement['name']}');
      }
    }

    print('=' * 60);
    print('');
  }

  // Add manual theme testing
  Future<void> testTheme(int level) async {
    print('🧪 Testing theme for level $level...');
    await _desktopEnhancer.applyThemeForLevel(level);
  }

  // Emergency restore
  Future<void> restoreDesktop() async {
    print('🔧 Restoring desktop to backup...');
    await _desktopEnhancer.restoreBackup();
  }

  // Refresh base config from system
  Future<void> refreshConfig() async {
    print('🔄 Refreshing base config from system...');
    await _desktopEnhancer.refreshBaseConfig();
  }

  // Add productivity commands
  void boostXP(ActivityEvent reason, {int amount = 100}) {
    _dbManager.updateDailyStats(amount, 0, 0);
    print('🚀 Manual XP boost: +$amount XP for $reason');

    // Send manual boost notification
    _xpNotificationManager.showXPGain(source: 'manual_boost', xpGained: amount, activity: reason);
  }

  void addMilestone(String milestone, {int xpReward = 200}) {
    final achievement = {
      'name': 'Milestone: $milestone',
      'description': 'Completed: $milestone',
      'xp_reward': xpReward,
    };

    final stats = _dbManager.getTodayStats();
    _awardAchievement(achievement, stats['level']);
  }

  Future<void> _pollActivity() async {
    try {
      if (_idleMonitor.status != IdleStatus.active) return;

      // Use window manager abstraction if available
      if (_windowManager != null) {
        final windowInfo = await _windowManager!.getCurrentWindow();
        if (windowInfo == null) return;

        final currentApp = windowInfo['application'] as String? ?? 'unknown';
        final currentWindowTitle = windowInfo['title'] as String? ?? '';
        final now = _timeProvider.now();

        await _checkZoomActivity(now);

        if (_lastActiveWindow != currentApp || _lastActiveWindowTitle != currentWindowTitle) {
          if (_lastActiveWindow != null && _lastActivityTime != null) {
            _saveActivityEvent(
              _lastActiveWindow!,
              now.difference(_lastActivityTime!).inSeconds,
              _lastActiveWindowTitle ?? '',
            );
          }

          _lastActiveWindow = currentApp;
          _lastActiveWindowTitle = currentWindowTitle;
          _lastActivityTime = now;
          final windowManager = WindowManagerDetector.instance.detect();
          print('Switched to: $currentApp (via ${windowManager.toString()})');
        }
      } else {
        // Fallback to Hyprland-specific approach if window manager is not available
        print('Warning: No window manager available, attempting Hyprland fallback');
        await _pollActivityHyprlandFallback();
      }
    } catch (e) {
      print('Error polling activity: $e');
      // Try fallback on error
      if (_windowManager != null) {
        print('Attempting fallback polling method...');
        await _pollActivityHyprlandFallback();
      }
    }
  }

  Future<void> _pollActivityHyprlandFallback() async {
    try {
      final result = await Process.run('hyprctl', ['activewindow', '-j']);
      if (result.exitCode != 0) return;

      final windowData = jsonDecode(result.stdout);
      final currentApp = windowData['class'] as String? ?? 'unknown';
      final currentWindowTitle = windowData['title'] as String? ?? '';
      final now = _timeProvider.now();

      await _checkZoomActivity(now);

      if (_lastActiveWindow != currentApp || _lastActiveWindowTitle != currentWindowTitle) {
        if (_lastActiveWindow != null && _lastActivityTime != null) {
          _saveActivityEvent(
            _lastActiveWindow!,
            now.difference(_lastActivityTime!).inSeconds,
            _lastActiveWindowTitle ?? '',
          );
        }

        _lastActiveWindow = currentApp;
        _lastActiveWindowTitle = currentWindowTitle;
        _lastActivityTime = now;
        print('Switched to: $currentApp (Hyprland fallback)');
      }
    } catch (e) {
      print('Error in Hyprland fallback polling: $e');
    }
  }

  Future<void> _checkZoomActivity(DateTime now) async {
    final currentZoomStatus = await _zoomDetector!.getZoomStatus();

    if (_lastZoomStatus != currentZoomStatus) {
      if (_lastZoomStatus != ZoomStatus.none && _lastZoomStatusTime != null) {
        final duration = now.difference(_lastZoomStatusTime!).inSeconds;
        if (duration > 10) {
          _saveZoomActivity(_lastZoomStatus, duration);
        }
      }

      _lastZoomStatus = currentZoomStatus;
      _lastZoomStatusTime = now;

      if (currentZoomStatus != ZoomStatus.none) {
        print('Zoom status changed to: $currentZoomStatus');
      }
    }
  }

  void _saveZoomActivity(ZoomStatus status, int durationSeconds) {
    final event = ActivityEvent(
      type: ActivityEventType.meetings,
      application: 'zoom',
      metadata: jsonEncode({'status': status.name, 'duration': durationSeconds}),
      timestamp: _timeProvider.now(),
    );

    _dbManager.saveActivityEvent(
      event.type.toString(),
      event.application,
      event.metadata,
      event.timestamp.millisecondsSinceEpoch,
      durationSeconds,
    );

    final xp = _calculateZoomXP(status, durationSeconds);
    final meetingTime = _isMeetingActivityZoom(status) ? durationSeconds : 0;

    _dbManager.updateDailyStats(xp, 0, meetingTime);
    print('Logged zoom activity: ${status.name} for ${durationSeconds}s (+$xp XP)');

    // Send zoom activity XP notification
    if (xp > 0) {
      _xpNotificationManager.showXPGain(
        source: 'meeting',
        xpGained: xp,
        activity: event,
        durationMinutes: (durationSeconds / 60).round(),
      );
    }
  }

  bool _isMeetingActivityZoom(ZoomStatus status) {
    return status == ZoomStatus.activeMeeting || status == ZoomStatus.backgroundMeeting;
  }

  int _calculateZoomXP(ZoomStatus status, int durationSeconds) {
    final minutes = (durationSeconds / 60).round();

    switch (status) {
      case ZoomStatus.activeMeeting:
        return minutes * 8;
      case ZoomStatus.backgroundMeeting:
        return minutes * 5;
      case ZoomStatus.zoomFocused:
        return minutes * 2;
      case ZoomStatus.zoomBackground:
      case ZoomStatus.none:
        return 0;
    }
  }

  void _saveActivityEvent(String application, int durationSeconds, String title) {
    if (durationSeconds < _activityDurationCutoffSeconds) return;

    // Check for user classification first
    final userClassification = _dbManager.getApplicationClassification(application);
    final activityType = ActivityEventType.categorize(
      eventId: userClassification,
      applicationId: application,
      applicationTitle: title,
    );

    // If no user classification exists and it falls back to uncategorized, track as unclassified
    if (userClassification == null && activityType == ActivityEventType.uncategorized) {
      _dbManager.trackUnclassifiedApplication(application);
    }

    final event = ActivityEvent(
      type: activityType,
      application: application,
      metadata: jsonEncode({'title': title, 'duration': durationSeconds}),
      timestamp: _timeProvider.now(),
    );

    _dbManager.saveActivityEvent(
      event.type.id,
      event.application,
      event.metadata,
      event.timestamp.millisecondsSinceEpoch,
      durationSeconds,
    );

    final xp = _calculateXP(event.type, durationSeconds);
    final focusTime = _isFocusActivity(event.type) ? durationSeconds : 0;
    final meetingTime = _isMeetingActivity(event.type) ? durationSeconds : 0;

    _dbManager.updateDailyStats(xp, focusTime, meetingTime);
    print('Logged: ${event.type} for ${durationSeconds}s (+$xp XP)');

    // Show cursor overlay for XP gain
    _cursorOverlayManager?.showXPNumber(xp);

    // Send activity XP notification
    _xpNotificationManager.showXPGain(
      source: 'activity',
      xpGained: xp,
      activity: event,
      durationMinutes: (durationSeconds / 60).round(),
    );

    // Broadcast updated stats and XP breakdown via WebSocket
    _broadcastStatsUpdate();
    _broadcastXPBreakdownUpdate();
  }

  bool _isFocusActivity(ActivityEventType activityType) {
    return activityType == ActivityEventType.coding || activityType == ActivityEventType.focusedBrowsing;
  }

  bool _isMeetingActivity(ActivityEventType activityType) {
    return activityType == ActivityEventType.meetings;
  }

  Map<String, dynamic> getTodayStats() => _dbManager.getTodayStats();
  Map<String, dynamic> getStreakStats() => _dbManager.getStreakStats();

  /// Manually trigger level up check (useful for testing)
  Future<void> checkForLevelUpNow() async {
    await _checkForLevelUp();
  }
  
  /// Get the cursor overlay manager (used for testing)
  CursorOverlayManager? get cursorOverlayManager => _cursorOverlayManager;

  // WebSocket broadcasting methods
  void _broadcastLevelUp(int newLevel) {
    if (WebSocketManager.instance.hasConnections) {
      final message = {
        'type': 'level_up',
        'data': {'level': newLevel},
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };
      WebSocketManager.instance.broadcast(message);
      Logger.info('Broadcasted level up to WebSocket clients: Level $newLevel');
    }
  }

  void _broadcastAchievementUnlocked(Map<String, dynamic> achievement) {
    if (WebSocketManager.instance.hasConnections) {
      final message = {
        'type': 'achievement_unlocked',
        'data': achievement,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };
      WebSocketManager.instance.broadcast(message);
      Logger.info('Broadcasted achievement to WebSocket clients: ${achievement['name']}');
    }
  }

  void _broadcastStatsUpdate() {
    if (WebSocketManager.instance.hasConnections) {
      final stats = _dbManager.getTodayStats();
      final streaks = _dbManager.getStreakStats();
      final recentActivity = _dbManager.getRecentActivity(5);

      final statsData = {
        'today': stats,
        'streaks': streaks,
        'recent_activity': recentActivity
            .map(
              (row) => {
                'type': row['type'],
                'application': row['application'],
                'timestamp': row['timestamp'],
                'duration_seconds': row['duration_seconds'],
              },
            )
            .toList(),
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };

      final message = {'type': 'stats_update', 'data': statsData, 'timestamp': DateTime.now().millisecondsSinceEpoch};
      WebSocketManager.instance.broadcast(message);
      Logger.info('Broadcasted stats update to WebSocket clients');
    }
  }

  void _broadcastXPBreakdownUpdate() {
    if (WebSocketManager.instance.hasConnections) {
      final breakdown = _dbManager.getTodayXPBreakdown();
      final message = {
        'type': 'xp_breakdown_update',
        'data': breakdown,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };
      WebSocketManager.instance.broadcast(message);
      Logger.info('Broadcasted XP breakdown update to WebSocket clients');
    }
  }

  void _broadcastFocusSessionComplete(int durationMinutes, int bonusXP) {
    if (WebSocketManager.instance.hasConnections) {
      final sessionData = {
        'duration_minutes': durationMinutes,
        'bonus_xp': bonusXP,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };

      final message = {
        'type': 'focus_session_complete',
        'data': sessionData,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };
      WebSocketManager.instance.broadcast(message);
      Logger.info('Broadcasted focus session completion to WebSocket clients: ${durationMinutes}min, +${bonusXP}XP');
    }
  }
}
