import 'dart:async';
import 'dart:io';

import 'package:xp_nix/src/models/activity_event.dart';

enum LogLevel {
  debug(0, 'DEBUG'),
  info(1, 'INFO'),
  warn(2, 'WARN'),
  error(3, 'ERROR');

  const LogLevel(this.value, this.name);
  final int value;
  final String name;
}

class Logger {
  static Logger? _instance;
  static Logger get instance => _instance ??= Logger._();

  Logger._();

  LogLevel _currentLevel = LogLevel.info;
  String _logDirectory = 'logs';
  int _maxFileSizeMB = 10;
  int _maxFiles = 5;
  File? _currentLogFile;
  final StreamController<String> _logStreamController = StreamController<String>.broadcast();

  Stream<String> get logStream => _logStreamController.stream;

  Future<void> initialize({
    LogLevel level = LogLevel.info,
    String logDirectory = 'logs',
    int maxFileSizeMB = 10,
    int maxFiles = 5,
  }) async {
    _currentLevel = level;
    _logDirectory = logDirectory;
    _maxFileSizeMB = maxFileSizeMB;
    _maxFiles = maxFiles;

    // Clean up old log files first
    await _cleanupOldLogs();
    
    await _setupLogFile();
    _info('Logger initialized with level: ${level.name}');
  }

  Future<void> _setupLogFile() async {
    final logDir = Directory(_logDirectory);
    if (!await logDir.exists()) {
      await logDir.create(recursive: true);
    }

    // Always create a new log file with current timestamp
    final timestamp = DateTime.now().toIso8601String().replaceAll(':', '-').split('.')[0];
    _currentLogFile = File('$_logDirectory/xp_nix_$timestamp.log');

    // Create the file if it doesn't exist
    if (!await _currentLogFile!.exists()) {
      await _currentLogFile!.create();
    }
  }

  Future<void> _rotateLogIfNeeded() async {
    if (_currentLogFile == null) return;

    try {
      final stats = await _currentLogFile!.stat();
      final fileSizeMB = stats.size / (1024 * 1024);

      if (fileSizeMB > _maxFileSizeMB) {
        await _cleanupOldLogs();
        await _setupLogFile();
        _info('Log file rotated due to size limit');
      }
    } catch (e) {
      // If we can't check file size, just continue
      print('Could not check log file size: $e');
    }
  }

  Future<void> _cleanupOldLogs() async {
    final logDir = Directory(_logDirectory);
    if (!await logDir.exists()) return;

    try {
      final logFiles =
          await logDir.list().where((entity) => entity is File && entity.path.endsWith('.log')).cast<File>().toList();

      logFiles.sort((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()));

      if (logFiles.length >= _maxFiles) {
        for (int i = _maxFiles - 1; i < logFiles.length; i++) {
          print('Deleting old log file: ${logFiles[i].path}');
          await logFiles[i].delete();
        }
      }
    } catch (e) {
      print('Could not cleanup old logs: $e');
    }
  }

  void _log(LogLevel level, String message, [Object? error, StackTrace? stackTrace]) {
    if (level.value < _currentLevel.value) return;

    final timestamp = DateTime.now().toIso8601String();
    final logEntry = '[$timestamp] [${level.name}] $message';

    // Print to console
    print(logEntry);

    // Add to stream for real-time monitoring
    _logStreamController.add(logEntry);

    // Write to file asynchronously
    _writeToFileAsync(logEntry, error, stackTrace);
  }

  void _writeToFileAsync(String logEntry, [Object? error, StackTrace? stackTrace]) {
    // Use Future.microtask to avoid blocking the current execution
    Future.microtask(() async {
      await _writeToFile(logEntry, error, stackTrace);
    });
  }

  Future<void> _writeToFile(String logEntry, [Object? error, StackTrace? stackTrace]) async {
    if (_currentLogFile == null) return;

    try {
      final buffer = StringBuffer();
      buffer.writeln(logEntry);

      if (error != null) {
        buffer.writeln('Error: $error');
      }

      if (stackTrace != null) {
        buffer.writeln('Stack trace: $stackTrace');
      }

      // Write to file using writeAsString with append mode
      await _currentLogFile!.writeAsString(buffer.toString(), mode: FileMode.append, flush: true);

      // Check if rotation is needed
      await _rotateLogIfNeeded();
    } catch (e) {
      // If writing fails, try to recreate the log file
      print('Log writing failed: $e');
      try {
        await _setupLogFile();
      } catch (setupError) {
        print('Could not recreate log file: $setupError');
      }
    }
  }

  // Static convenience methods
  static void debug(String message) => instance._log(LogLevel.debug, message);
  static void info(String message) => instance._log(LogLevel.info, message);
  static void warn(String message) => instance._log(LogLevel.warn, message);
  static void error(String message, [Object? error, StackTrace? stackTrace]) =>
      instance._log(LogLevel.error, message, error, stackTrace);

  // Instance methods for when you have a logger instance
  // ignore: unused_element
  void _debug(String message) => _log(LogLevel.debug, message);
  // ignore: unused_element
  void _info(String message) => _log(LogLevel.info, message);
  // ignore: unused_element
  void _warn(String message) => _log(LogLevel.warn, message);
  // ignore: unused_element
  void _error(String message, [Object? error, StackTrace? stackTrace]) =>
      _log(LogLevel.error, message, error, stackTrace);

  // Activity-specific logging methods
  static void logActivity(String activityType, String application, int durationSeconds, int xpGained) {
    info('ACTIVITY: $activityType in $application for ${durationSeconds}s (+$xpGained XP)');
  }

  static void logFocusSession(int minutes, int bonusXP) {
    info('FOCUS_SESSION: ${minutes}min session completed (+$bonusXP XP)');
  }

  static void logLevelUp(int oldLevel, int newLevel, int totalXP) {
    info('LEVEL_UP: $oldLevel → $newLevel (Total XP: $totalXP)');
  }

  static void logAchievement(String name, String description, int xpReward) {
    info('ACHIEVEMENT: $name - $description (+$xpReward XP)');
  }

  static void logConfigChange(String path, dynamic oldValue, dynamic newValue) {
    info('CONFIG_CHANGE: $path changed from $oldValue to $newValue');
  }

  static void logXPCalculation(ActivityEventType activityType, int baseXP, double multiplier, int finalXP) {
    debug('XP_CALC: ${activityType.displayName} base=$baseXP × $multiplier = $finalXP');
  }

  static void logIdleStateChange(bool isIdle, Duration? idleDuration) {
    if (isIdle) {
      debug('IDLE: User became idle');
    } else {
      final duration = idleDuration?.inMinutes ?? 0;
      info('ACTIVE: User became active after ${duration}min idle');
    }
  }

  static void logZoomStatusChange(String oldStatus, String newStatus) {
    info('ZOOM_STATUS: $oldStatus → $newStatus');
  }

  static void logThemeChange(int level, String themeName) {
    info('THEME_CHANGE: Applied theme "$themeName" for level $level');
  }

  static void logXPGain(String source, int xpGained, String activity, int currentXP, int currentLevel) {
    info('XP_GAIN: +$xpGained from $source ($activity) - Total: $currentXP XP, Level: $currentLevel');
  }

  // Performance logging
  static void logPerformanceMetric(String operation, Duration duration) {
    debug('PERFORMANCE: $operation took ${duration.inMilliseconds}ms');
  }

  // Get recent log entries for dashboard
  Future<List<String>> getRecentLogs([int count = 100]) async {
    if (_currentLogFile == null || !await _currentLogFile!.exists()) {
      return [];
    }

    try {
      final lines = await _currentLogFile!.readAsLines();
      return lines.reversed.take(count).toList();
    } catch (e) {
      _error('Failed to read log file: $e');
      return [];
    }
  }

  // Get logs by level
  Future<List<String>> getLogsByLevel(LogLevel level, [int count = 50]) async {
    final allLogs = await getRecentLogs(count * 2);
    return allLogs.where((log) => log.contains('[${level.name}]')).take(count).toList();
  }

  // Update log level dynamically
  void setLogLevel(LogLevel level) {
    _currentLevel = level;
    info('Log level changed to: ${level.name}');
  }

  Future<void> dispose() async {
    await _logStreamController.close();
  }
}
