import 'package:sqlite3/sqlite3.dart';
import '../models/activity_event.dart';

class DatabaseManager {
  final Database _db;

  DatabaseManager(this._db);

  void initDatabase() {
    _db.execute('''
      CREATE TABLE IF NOT EXISTS activity_events (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        type_id TEXT NOT NULL,
        application_id TEXT NOT NULL,
        metadata TEXT,
        timestamp INTEGER NOT NULL,
        duration_seconds INTEGER DEFAULT 0
      )
    ''');

    _db.execute('''
      CREATE TABLE IF NOT EXISTS daily_stats (
        date TEXT PRIMARY KEY,
        total_xp INTEGER DEFAULT 0,
        level INTEGER DEFAULT 1,
        focus_time_seconds INTEGER DEFAULT 0,
        meeting_time_seconds INTEGER DEFAULT 0,
        level_up_timestamp INTEGER DEFAULT 0
      )
    ''');

    _db.execute('''
      CREATE TABLE IF NOT EXISTS focus_sessions (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT NOT NULL,
        duration_minutes INTEGER NOT NULL,
        bonus_xp INTEGER NOT NULL,
        timestamp INTEGER NOT NULL
      )
    ''');

    _db.execute('''
      CREATE TABLE IF NOT EXISTS achievements (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        description TEXT NOT NULL,
        xp_reward INTEGER NOT NULL,
        achieved_at INTEGER NOT NULL,
        level_at_achievement INTEGER NOT NULL
      )
    ''');

    _db.execute('''
      CREATE TABLE IF NOT EXISTS theme_history (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        level INTEGER NOT NULL,
        theme_name TEXT NOT NULL,
        applied_at INTEGER NOT NULL
      )
    ''');

    _db.execute('''
      CREATE TABLE IF NOT EXISTS application_classifications (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        application_name TEXT NOT NULL UNIQUE,
        category_id TEXT NOT NULL,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL
      )
    ''');

    _db.execute('''
      CREATE TABLE IF NOT EXISTS unclassified_applications (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        application_name TEXT NOT NULL UNIQUE,
        first_seen INTEGER NOT NULL,
        last_seen INTEGER NOT NULL,
        occurrence_count INTEGER DEFAULT 1
      )
    ''');
  }

  void saveActivityEvent(String type, String application, String? metadata, int timestamp, int durationSeconds) {
    _db.execute(
      '''
      INSERT INTO activity_events (type_id, application_id, metadata, timestamp, duration_seconds)
      VALUES (?, ?, ?, ?, ?)
    ''',
      [type, application, metadata, timestamp, durationSeconds],
    );
  }

  void updateDailyStats(int xp, int focusSeconds, int meetingSeconds) {
    final today = DateTime.now().toIso8601String().substring(0, 10);

    _db.execute(
      '''
      INSERT INTO daily_stats (date, total_xp, focus_time_seconds, meeting_time_seconds)
      VALUES (?, ?, ?, ?)
      ON CONFLICT(date) DO UPDATE SET
        total_xp = total_xp + ?,
        focus_time_seconds = focus_time_seconds + ?,
        meeting_time_seconds = meeting_time_seconds + ?
    ''',
      [today, xp, focusSeconds, meetingSeconds, xp, focusSeconds, meetingSeconds],
    );

    final stats = _db.select('SELECT total_xp FROM daily_stats WHERE date = ?', [today]);
    if (stats.isNotEmpty) {
      final totalXP = stats.first['total_xp'] as int;
      final newLevel = calculateLevel(totalXP);

      _db.execute('UPDATE daily_stats SET level = ? WHERE date = ?', [newLevel, today]);
    }
  }

  void saveFocusSession(String date, int durationMinutes, int bonusXP, int timestamp) {
    _db.execute(
      '''
      INSERT INTO focus_sessions (date, duration_minutes, bonus_xp, timestamp)
      VALUES (?, ?, ?, ?)
    ''',
      [date, durationMinutes, bonusXP, timestamp],
    );
  }

  void saveAchievement(String name, String description, int xpReward, int achievedAt, int levelAtAchievement) {
    _db.execute(
      '''
      INSERT INTO achievements (name, description, xp_reward, achieved_at, level_at_achievement)
      VALUES (?, ?, ?, ?, ?)
    ''',
      [name, description, xpReward, achievedAt, levelAtAchievement],
    );
  }

  void recordThemeChange(int level, String themeName, int appliedAt) {
    _db.execute(
      '''
      INSERT INTO theme_history (level, theme_name, applied_at)
      VALUES (?, ?, ?)
    ''',
      [level, themeName, appliedAt],
    );
  }

  void updateLevelUpTimestamp(String date, int timestamp) {
    _db.execute(
      '''
      UPDATE daily_stats SET level_up_timestamp = ? WHERE date = ?
    ''',
      [timestamp, date],
    );
  }

  Map<String, dynamic> getTodayStats() {
    final today = DateTime.now().toIso8601String().substring(0, 10);
    final stats = _db.select('SELECT * FROM daily_stats WHERE date = ?', [today]);

    if (stats.isEmpty) {
      return {'level': 1, 'xp': 0, 'focus_time': 0, 'meeting_time': 0, 'focus_sessions': 0};
    }

    final row = stats.first;
    final focusSessions = _db.select('SELECT COUNT(*) as count FROM focus_sessions WHERE date = ?', [today]);
    final sessionCount = focusSessions.isNotEmpty ? focusSessions.first['count'] : 0;

    return {
      'level': row['level'],
      'xp': row['total_xp'],
      'focus_time': row['focus_time_seconds'],
      'meeting_time': row['meeting_time_seconds'],
      'focus_sessions': sessionCount,
    };
  }

  Map<String, dynamic> getStreakStats() {
    final result = _db.select('''
      SELECT date, focus_time_seconds 
      FROM daily_stats 
      WHERE focus_time_seconds > 0 
      ORDER BY date DESC 
      LIMIT 30
    ''');

    int currentStreak = 0;
    int longestStreak = 0;
    int tempStreak = 0;

    final today = DateTime.now();

    for (int i = 0; i < result.length; i++) {
      final dateStr = result[i]['date'] as String;
      final date = DateTime.parse(dateStr);
      final daysDiff = today.difference(date).inDays;

      if (daysDiff == i) {
        tempStreak++;
        if (i == 0) currentStreak = tempStreak;
      } else {
        if (tempStreak > longestStreak) longestStreak = tempStreak;
        tempStreak = 0;
      }
    }

    if (tempStreak > longestStreak) longestStreak = tempStreak;

    return {'current_streak': currentStreak, 'longest_streak': longestStreak};
  }

  List<Row> getRecentAchievements() {
    return _db.select('''
      SELECT name FROM achievements 
      WHERE DATE(achieved_at/1000, 'unixepoch') = DATE('now') 
      ORDER BY achieved_at DESC LIMIT 3
    ''');
  }

  bool hasAchievement(String name) {
    final today = DateTime.now().toIso8601String().substring(0, 10);
    final result = _db.select(
      '''
      SELECT COUNT(*) as count FROM achievements 
      WHERE name = ? AND DATE(achieved_at/1000, 'unixepoch') = ?
    ''',
      [name, today],
    );
    return result.isNotEmpty && (result.first['count'] as int) > 0;
  }

  int calculateLevel(int totalXP) {
    return (totalXP / 100).floor() + 1;
  }

  // Dashboard query methods
  List<Row> getStatsHistory(int days) {
    final history = <Row>[];
    final now = DateTime.now();

    for (int i = days - 1; i >= 0; i--) {
      final date = now.subtract(Duration(days: i));
      final dateStr = date.toIso8601String().substring(0, 10);

      final stats = _db.select('SELECT * FROM daily_stats WHERE date = ?', [dateStr]);

      if (stats.isNotEmpty) {
        history.addAll(stats);
      }
    }

    return history;
  }

  List<Row> getAllAchievements([int limit = 50]) {
    return _db.select(
      '''
      SELECT * FROM achievements 
      ORDER BY achieved_at DESC 
      LIMIT ?
    ''',
      [limit],
    );
  }

  List<Row> getRecentActivities([int limit = 100]) {
    return _db.select(
      '''
      SELECT * FROM activity_events 
      ORDER BY timestamp DESC 
      LIMIT ?
    ''',
      [limit],
    );
  }

  List<Row> getRecentFocusSessions([int limit = 50]) {
    return _db.select(
      '''
      SELECT * FROM focus_sessions 
      ORDER BY timestamp DESC 
      LIMIT ?
    ''',
      [limit],
    );
  }

  List<Row> getRecentActivity([int limit = 5]) {
    final today = DateTime.now();
    final startOfDay = DateTime(today.year, today.month, today.day).millisecondsSinceEpoch;
    final endOfDay = DateTime(today.year, today.month, today.day, 23, 59, 59, 999).millisecondsSinceEpoch;

    return _db.select(
      '''
      SELECT type_id, application_id, timestamp, duration_seconds 
      FROM activity_events
      WHERE timestamp >= ? AND timestamp <= ?
      ORDER BY timestamp DESC 
      LIMIT ?
    ''',
      [startOfDay, endOfDay, limit],
    );
  }

  List<Row> getDailyStatsForDate(String date) {
    return _db.select('SELECT * FROM daily_stats WHERE date = ?', [date]);
  }

  Map<String, int> getXPBreakdownForDate(String date) {
    final breakdown = <String, int>{};

    // Get activities for the date
    final startOfDay = DateTime.parse('${date}T00:00:00').millisecondsSinceEpoch;
    final endOfDay = DateTime.parse('${date}T23:59:59').millisecondsSinceEpoch;

    final activities = _db.select(
      '''
      SELECT type_id, application_id, duration_seconds 
      FROM activity_events 
      WHERE timestamp >= ? AND timestamp <= ?
    ''',
      [startOfDay, endOfDay],
    );

    // Calculate XP for each activity category using the new ActivityCategory system
    for (final activity in activities) {
      String type = activity['type_id'] as String;
      final application = activity['application_id'] as String;
      final durationSeconds = activity['duration_seconds'] as int;
      final durationMinutes = (durationSeconds / 60).ceil();

      // Determine category using the new ActivityCategory system
      String category;
      int xpPerMinute;

      // Check if user has classified this application
      final userClassification = getApplicationClassification(application);
      if (userClassification != null) {
        type = userClassification;
      }
      final activityCategory = ActivityEventType.categorize(eventId: type, applicationId: application);
      category = activityCategory.id;
      xpPerMinute = _getXPForActivityEventType(activityCategory);

      final xpEarned = durationMinutes * xpPerMinute;
      breakdown[category] = (breakdown[category] ?? 0) + xpEarned;
    }

    // Add focus session bonuses
    final focusSessions = _db.select(
      '''
      SELECT bonus_xp FROM focus_sessions WHERE date = ?
    ''',
      [date],
    );

    if (focusSessions.isNotEmpty) {
      final totalFocusBonus = focusSessions.fold<int>(0, (sum, session) => sum + (session['bonus_xp'] as int));
      breakdown['focus_session'] = totalFocusBonus;
    }

    // Add achievement XP
    final achievements = _db.select(
      '''
      SELECT xp_reward FROM achievements 
      WHERE DATE(achieved_at/1000, 'unixepoch') = ?
    ''',
      [date],
    );

    if (achievements.isNotEmpty) {
      final totalAchievementXP = achievements.fold<int>(
        0,
        (sum, achievement) => sum + (achievement['xp_reward'] as int),
      );
      breakdown['achievement'] = totalAchievementXP;
    }

    return breakdown;
  }

  /// Get XP per minute for a given ActivityEventType using exhaustive matching
  int _getXPForActivityEventType(ActivityEventType eventType) {
    switch (eventType) {
      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;
    }
  }

  Map<String, int> getTodayXPBreakdown() {
    final today = DateTime.now().toIso8601String().substring(0, 10);
    return getXPBreakdownForDate(today);
  }

  // Application classification methods
  void saveApplicationClassification(String applicationName, String categoryId) {
    final now = DateTime.now().millisecondsSinceEpoch;
    _db.execute(
      '''
      INSERT INTO application_classifications (application_name, category_id, created_at, updated_at)
      VALUES (?, ?, ?, ?)
      ON CONFLICT(application_name) DO UPDATE SET
        category_id = ?,
        updated_at = ?
    ''',
      [applicationName, categoryId, now, now, categoryId, now],
    );

    // Remove from unclassified if it exists
    _db.execute('DELETE FROM unclassified_applications WHERE application_name = ?', [applicationName]);
  }

  String? getApplicationClassification(String applicationName) {
    final result = _db.select('SELECT category_id FROM application_classifications WHERE application_name = ?', [
      applicationName,
    ]);
    return result.isNotEmpty ? result.first['category_id'] as String : null;
  }

  List<Row> getAllApplicationClassifications() {
    return _db.select('''
      SELECT * FROM application_classifications 
      ORDER BY application_name ASC
    ''');
  }

  void trackUnclassifiedApplication(String applicationName) {
    final now = DateTime.now().millisecondsSinceEpoch;
    _db.execute(
      '''
      INSERT INTO unclassified_applications (application_name, first_seen, last_seen, occurrence_count)
      VALUES (?, ?, ?, 1)
      ON CONFLICT(application_name) DO UPDATE SET
        last_seen = ?,
        occurrence_count = occurrence_count + 1
    ''',
      [applicationName, now, now, now],
    );
  }

  List<Row> getUnclassifiedApplications() {
    return _db.select('''
      SELECT * FROM unclassified_applications 
      ORDER BY occurrence_count DESC, last_seen DESC
    ''');
  }

  void deleteApplicationClassification(String applicationName) {
    _db.execute('DELETE FROM application_classifications WHERE application_name = ?', [applicationName]);
  }
}
