import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:test/test.dart';
import 'package:sqlite3/sqlite3.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../lib/src/database/database_manager.dart';
import '../lib/src/web/dashboard_server.dart';
import '../lib/src/web/websocket_manager.dart';
import '../lib/src/models/activity_event.dart';

void main() {
  group('WebSocket Integration Tests', () {
    late Database inMemoryDb;
    late DatabaseManager dbManager;
    late DashboardServer server;
    late WebSocketChannel wsChannel;
    final int testPort = 8081;

    setUpAll(() async {
      // Create in-memory database
      inMemoryDb = sqlite3.openInMemory();
      dbManager = DatabaseManager(inMemoryDb);
      dbManager.initDatabase();

      // Populate database with test data
      await _populateTestData(dbManager);

      // Start server with test database
      server = DashboardServer.withDatabase(dbManager);
      await server.start(testPort);

      // Give server time to start
      await Future.delayed(Duration(milliseconds: 100));
    });

    tearDownAll(() async {
      await wsChannel.sink.close();
      await server.stop();
      inMemoryDb.dispose();
    });

    test('WebSocket connection and message types', () async {
      // Connect to WebSocket
      wsChannel = WebSocketChannel.connect(
        Uri.parse('ws://localhost:$testPort/ws'),
      );

      final receivedMessages = <Map<String, dynamic>>[];

      // Listen for messages
      wsChannel.stream.listen(
        (message) {
          final data = jsonDecode(message as String) as Map<String, dynamic>;
          receivedMessages.add(data);
        },
        onError: (error) {
          fail('WebSocket error: $error');
        },
      );

      // Test 1: Ping/Pong
      print('Testing ping/pong...');
      wsChannel.sink.add(jsonEncode({
        'type': 'ping',
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      }));

      // Wait for pong response
      await Future.delayed(Duration(milliseconds: 200));
      expect(receivedMessages.length, equals(1));
      expect(receivedMessages[0]['type'], equals('pong'));
      expect(receivedMessages[0]['timestamp'], isA<int>());
      
      receivedMessages.clear();

      // Test 2: Stats Update Message
      print('Testing stats update broadcast...');
      final wsManager = WebSocketManager.instance;
      
      // Create and broadcast stats update
      final statsData = {
        'today': {
          'level': 5,
          'xp': 450,
          'focus_time': 7200,
          'meeting_time': 3600,
          'focus_sessions': 3,
        },
        'streaks': {
          'current_streak': 7,
          'longest_streak': 15,
        },
        'recent_activity': [
          {
            'type': 'coding',
            'application': 'vscode',
            'timestamp': DateTime.now().millisecondsSinceEpoch,
            'duration_seconds': 1800,
          }
        ],
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };

      wsManager.broadcast({
        'type': 'stats_update',
        'data': statsData,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });

      await Future.delayed(Duration(milliseconds: 100));
      expect(receivedMessages.length, equals(1));
      expect(receivedMessages[0]['type'], equals('stats_update'));
      expect(receivedMessages[0]['data']['today']['level'], equals(5));
      expect(receivedMessages[0]['data']['today']['xp'], equals(450));
      
      receivedMessages.clear();

      // Test 3: XP Breakdown Update
      print('Testing XP breakdown update...');
      final xpBreakdown = {
        'coding': 300,
        'focused_browsing': 120,
        'collaboration': 80,
        'meetings': 60,
        'focus_session': 100,
      };

      wsManager.broadcast({
        'type': 'xp_breakdown_update',
        'data': xpBreakdown,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });

      await Future.delayed(Duration(milliseconds: 100));
      expect(receivedMessages.length, equals(1));
      expect(receivedMessages[0]['type'], equals('xp_breakdown_update'));
      expect(receivedMessages[0]['data']['coding'], equals(300));
      expect(receivedMessages[0]['data']['focused_browsing'], equals(120));
      
      receivedMessages.clear();

      // Test 4: Achievement Unlocked
      print('Testing achievement unlocked...');
      final achievement = {
        'id': 1,
        'name': 'Code Warrior',
        'description': 'Spent 10 hours coding in a single day',
        'xp_reward': 100,
        'achieved_at': DateTime.now().millisecondsSinceEpoch,
        'level_at_achievement': 5,
      };

      wsManager.broadcast({
        'type': 'achievement_unlocked',
        'data': achievement,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });

      await Future.delayed(Duration(milliseconds: 100));
      expect(receivedMessages.length, equals(1));
      expect(receivedMessages[0]['type'], equals('achievement_unlocked'));
      expect(receivedMessages[0]['data']['name'], equals('Code Warrior'));
      expect(receivedMessages[0]['data']['xp_reward'], equals(100));
      
      receivedMessages.clear();

      // Test 5: Level Up
      print('Testing level up...');
      wsManager.broadcast({
        'type': 'level_up',
        'data': {'level': 6},
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });

      await Future.delayed(Duration(milliseconds: 100));
      expect(receivedMessages.length, equals(1));
      expect(receivedMessages[0]['type'], equals('level_up'));
      expect(receivedMessages[0]['data']['level'], equals(6));
      
      receivedMessages.clear();

      // Test 6: Focus Session Complete
      print('Testing focus session complete...');
      final focusSession = {
        'id': 1,
        'date': DateTime.now().toIso8601String().substring(0, 10),
        'duration_minutes': 45,
        'bonus_xp': 50,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };

      wsManager.broadcast({
        'type': 'focus_session_complete',
        'data': focusSession,
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });

      await Future.delayed(Duration(milliseconds: 100));
      expect(receivedMessages.length, equals(1));
      expect(receivedMessages[0]['type'], equals('focus_session_complete'));
      expect(receivedMessages[0]['data']['duration_minutes'], equals(45));
      expect(receivedMessages[0]['data']['bonus_xp'], equals(50));
      
      receivedMessages.clear();

      print('All WebSocket message types tested successfully!');
    });

    test('Database integration with WebSocket messages', () async {
      // Test that database queries work correctly and can feed WebSocket messages
      
      // Test today's stats
      final todayStats = dbManager.getTodayStats();
      expect(todayStats['level'], greaterThan(0));
      expect(todayStats['xp'], greaterThan(0));
      
      // Test XP breakdown
      final xpBreakdown = dbManager.getTodayXPBreakdown();
      expect(xpBreakdown, isNotEmpty);
      expect(xpBreakdown.containsKey('coding'), isTrue);
      
      // Test achievements
      final achievements = dbManager.getAllAchievements();
      expect(achievements.length, greaterThan(0));
      
      // Test recent activities
      final activities = dbManager.getRecentActivity();
      expect(activities.length, greaterThan(0));
      
      // Test focus sessions
      final focusSessions = dbManager.getRecentFocusSessions();
      expect(focusSessions.length, greaterThan(0));
      
      print('Database integration verified successfully!');
    });

    test('WebSocket connection management', () async {
      final wsManager = WebSocketManager.instance;
      final initialConnectionCount = wsManager.connectionCount;
      
      // Connect additional WebSocket
      final wsChannel2 = WebSocketChannel.connect(
        Uri.parse('ws://localhost:$testPort/ws'),
      );
      
      await Future.delayed(Duration(milliseconds: 100));
      expect(wsManager.connectionCount, equals(initialConnectionCount + 1));
      
      // Test broadcasting to multiple connections
      final receivedMessages2 = <Map<String, dynamic>>[];
      
      // Only listen to the new channel to avoid "already listened" error
      wsChannel2.stream.listen((message) {
        receivedMessages2.add(jsonDecode(message as String));
      });
      
      // Broadcast a message
      wsManager.broadcast({
        'type': 'test_broadcast',
        'data': {'message': 'Hello all connections!'},
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      });
      
      await Future.delayed(Duration(milliseconds: 100));
      
      // Verify the new connection received the broadcast
      expect(receivedMessages2.length, greaterThan(0));
      expect(receivedMessages2.last['type'], equals('test_broadcast'));
      
      // Close second connection
      await wsChannel2.sink.close();
      await Future.delayed(Duration(milliseconds: 100));
      
      expect(wsManager.connectionCount, equals(initialConnectionCount));
      
      print('WebSocket connection management tested successfully!');
    });
  });
}

Future<void> _populateTestData(DatabaseManager dbManager) async {
  final now = DateTime.now();
  final today = now.toIso8601String().substring(0, 10);
  final timestamp = now.millisecondsSinceEpoch;

  // Add activity events
  dbManager.saveActivityEvent('coding', 'vscode', '{"project": "test"}', timestamp - 3600000, 1800);
  dbManager.saveActivityEvent('focused_browsing', 'firefox', '{"url": "docs.flutter.dev"}', timestamp - 7200000, 1200);
  dbManager.saveActivityEvent('collaboration', 'slack', '{"channel": "dev-team"}', timestamp - 10800000, 900);
  dbManager.saveActivityEvent('meetings', 'zoom', '{"meeting": "standup"}', timestamp - 14400000, 1800);
  dbManager.saveActivityEvent('misc', 'keepassxc', null, timestamp - 18000000, 300);

  // Update daily stats
  dbManager.updateDailyStats(450, 7200, 3600);

  // Add focus sessions
  dbManager.saveFocusSession(today, 45, 50, timestamp - 3600000);
  dbManager.saveFocusSession(today, 30, 30, timestamp - 7200000);
  dbManager.saveFocusSession(today, 60, 75, timestamp - 10800000);

  // Add achievements
  dbManager.saveAchievement(
    'First Steps',
    'Complete your first activity tracking session',
    25,
    timestamp - 86400000,
    1,
  );
  dbManager.saveAchievement(
    'Code Warrior',
    'Spend 10 hours coding in a single day',
    100,
    timestamp - 43200000,
    3,
  );
  dbManager.saveAchievement(
    'Focus Master',
    'Complete 5 focus sessions in one day',
    75,
    timestamp - 21600000,
    4,
  );

  // Add application classifications
  dbManager.saveApplicationClassification('vscode', 'coding');
  dbManager.saveApplicationClassification('firefox', 'focused_browsing');
  dbManager.saveApplicationClassification('slack', 'collaboration');
  dbManager.saveApplicationClassification('zoom', 'meetings');

  // Add some unclassified applications
  dbManager.trackUnclassifiedApplication('unknown_app_1');
  dbManager.trackUnclassifiedApplication('unknown_app_2');

  print('Test data populated successfully');
}
