import 'dart:async';
import 'dart:collection';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:logger/logger.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:spotiflac_android/constants/app_info.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';

const int _maxLogMessageLength = 500;
const String _redactedValue = '[REDACTED]';

final RegExp _authorizationBearerPattern = RegExp(
  r'\bAuthorization\b\s*[:=]\s*Bearer\s+[A-Za-z0-9._~+/\-]+=*',
  caseSensitive: false,
);

final RegExp _genericSensitiveKeyValuePattern = RegExp(
  r'\b(access[_\s-]?token|refresh[_\s-]?token|id[_\s-]?token|client[_\s-]?secret|authorization|password|api[_\s-]?key)\b(\s*[:=]\s*)([^\s,;]+)',
  caseSensitive: false,
);

final RegExp _sensitiveQueryPattern = RegExp(
  r'([?&](?:access_token|refresh_token|id_token|token|client_secret|api_key|apikey|password)=)[^&\s]+',
  caseSensitive: false,
);

final RegExp _bearerTokenPattern = RegExp(
  r'\bBearer\s+[A-Za-z0-9._~+/\-]+=*',
  caseSensitive: false,
);

String _truncateLogText(String value, {int maxLength = _maxLogMessageLength}) {
  if (value.length <= maxLength) {
    return value;
  }
  return '${value.substring(0, maxLength)}...[truncated]';
}

String _redactSensitiveText(String value) {
  var redacted = value;

  redacted = redacted.replaceAllMapped(_authorizationBearerPattern, (_) {
    return 'Authorization: Bearer $_redactedValue';
  });

  redacted = redacted.replaceAllMapped(_genericSensitiveKeyValuePattern, (
    match,
  ) {
    final key = match.group(1) ?? '';
    final delimiter = match.group(2) ?? '=';
    return '$key$delimiter$_redactedValue';
  });

  redacted = redacted.replaceAllMapped(_sensitiveQueryPattern, (match) {
    final prefix = match.group(1) ?? '';
    return '$prefix$_redactedValue';
  });

  redacted = redacted.replaceAllMapped(_bearerTokenPattern, (_) {
    return 'Bearer $_redactedValue';
  });

  return redacted;
}

class LogEntry {
  final DateTime timestamp;
  final String level;
  final String tag;
  final String message;
  final String? error;
  final bool isFromGo;

  LogEntry({
    required this.timestamp,
    required this.level,
    required this.tag,
    required this.message,
    this.error,
    this.isFromGo = false,
  });

  String get formattedTime {
    final h = timestamp.hour.toString().padLeft(2, '0');
    final m = timestamp.minute.toString().padLeft(2, '0');
    final s = timestamp.second.toString().padLeft(2, '0');
    final ms = timestamp.millisecond.toString().padLeft(3, '0');
    return '$h:$m:$s.$ms';
  }

  @override
  String toString() {
    final errorPart = error != null ? ' | $error' : '';
    final goPart = isFromGo ? ' [Go]' : '';
    return '[$formattedTime] [$level]$goPart [$tag] $message$errorPart';
  }
}

class LogBuffer extends ChangeNotifier {
  static final LogBuffer _instance = LogBuffer._internal();
  factory LogBuffer() => _instance;
  LogBuffer._internal();

  static const int maxEntries = 500;
  static const Duration _goLogPollingInterval = Duration(milliseconds: 800);
  final Queue<LogEntry> _entries = Queue<LogEntry>();
  Timer? _goLogTimer;
  int _lastGoLogIndex = 0;
  bool _isFetchingGoLogs = false;

  static bool _loggingEnabled = false;
  static bool get loggingEnabled => _loggingEnabled;
  static set loggingEnabled(bool value) {
    _loggingEnabled = value;
    if (value) {
      PlatformBridge.setGoLoggingEnabled(true).catchError((_) {});
    } else {
      PlatformBridge.setGoLoggingEnabled(false).catchError((_) {});
    }
  }

  List<LogEntry> get entries => _entries.toList();
  int get length => _entries.length;

  void add(LogEntry entry) {
    if (!_loggingEnabled && entry.level != 'ERROR' && entry.level != 'FATAL') {
      return;
    }

    final sanitizedMessage = _truncateLogText(
      _redactSensitiveText(entry.message),
    );
    final sanitizedError = entry.error != null
        ? _truncateLogText(_redactSensitiveText(entry.error!))
        : null;
    final sanitizedEntry =
        (sanitizedMessage == entry.message && sanitizedError == entry.error)
        ? entry
        : LogEntry(
            timestamp: entry.timestamp,
            level: entry.level,
            tag: entry.tag,
            message: sanitizedMessage,
            error: sanitizedError,
            isFromGo: entry.isFromGo,
          );

    if (_entries.length >= maxEntries) {
      _entries.removeFirst();
    }
    _entries.add(sanitizedEntry);
    notifyListeners();
  }

  void startGoLogPolling() {
    _goLogTimer?.cancel();
    _goLogTimer = Timer.periodic(_goLogPollingInterval, (_) async {
      if (_isFetchingGoLogs) return;
      _isFetchingGoLogs = true;
      try {
        await _fetchGoLogs();
      } finally {
        _isFetchingGoLogs = false;
      }
    });
  }

  void stopGoLogPolling() {
    _goLogTimer?.cancel();
    _goLogTimer = null;
    _isFetchingGoLogs = false;
  }

  Future<void> _fetchGoLogs() async {
    try {
      final result = await PlatformBridge.getGoLogsSince(_lastGoLogIndex);
      final logs = result['logs'] as List<dynamic>? ?? [];
      final nextIndex = result['next_index'] as int? ?? _lastGoLogIndex;
      final keepNonErrorLogs = _loggingEnabled;

      for (final log in logs) {
        final level = log['level'] as String? ?? 'INFO';
        if (!keepNonErrorLogs && level != 'ERROR' && level != 'FATAL') {
          continue;
        }

        final timestamp = log['timestamp'] as String? ?? '';
        final tag = log['tag'] as String? ?? 'Go';
        final message = log['message'] as String? ?? '';

        DateTime parsedTime = DateTime.now();
        if (timestamp.isNotEmpty) {
          try {
            final parts = timestamp.split(':');
            if (parts.length >= 3) {
              final secParts = parts[2].split('.');
              parsedTime = DateTime(
                parsedTime.year,
                parsedTime.month,
                parsedTime.day,
                int.parse(parts[0]),
                int.parse(parts[1]),
                int.parse(secParts[0]),
                secParts.length > 1 ? int.parse(secParts[1]) : 0,
              );
            }
          } catch (_) {}
        }

        add(
          LogEntry(
            timestamp: parsedTime,
            level: level,
            tag: tag,
            message: message,
            isFromGo: true,
          ),
        );
      }

      _lastGoLogIndex = nextIndex;
    } catch (e) {
      if (kDebugMode) {
        debugPrint('Failed to fetch Go logs: $e');
      }
    }
  }

  void clear() {
    _entries.clear();
    _lastGoLogIndex = 0;
    PlatformBridge.clearGoLogs().catchError((_) {});
    notifyListeners();
  }

  String export() {
    final buffer = StringBuffer();
    buffer.writeln('SpotiFLAC Log Export');
    buffer.writeln('Generated: ${DateTime.now().toIso8601String()}');
    buffer.writeln('Entries: ${_entries.length}');
    buffer.writeln('=' * 60);
    buffer.writeln();
    for (final entry in _entries) {
      buffer.writeln(entry.toString());
    }
    return buffer.toString();
  }

  Future<String> exportWithDeviceInfo() async {
    final buffer = StringBuffer();

    buffer.writeln('=' * 60);
    buffer.writeln('SPOTIFLAC LOG EXPORT');
    buffer.writeln('=' * 60);
    buffer.writeln();

    buffer.writeln('--- App Information ---');
    buffer.writeln(
      'App Version: ${AppInfo.version} (Build ${AppInfo.buildNumber})',
    );
    buffer.writeln('Generated: ${DateTime.now().toIso8601String()}');
    buffer.writeln();

    buffer.writeln('--- Device Information ---');
    try {
      final deviceInfo = DeviceInfoPlugin();

      if (Platform.isAndroid) {
        final android = await deviceInfo.androidInfo;
        buffer.writeln('Platform: Android');
        buffer.writeln('Device: ${android.manufacturer} ${android.model}');
        buffer.writeln('Brand: ${android.brand}');
        buffer.writeln(
          'Android Version: ${android.version.release} (SDK ${android.version.sdkInt})',
        );
        buffer.writeln('Build ID: ${android.id}');
        if (android.version.securityPatch != null &&
            android.version.securityPatch!.isNotEmpty) {
          buffer.writeln('Security Patch: ${android.version.securityPatch}');
        }
        buffer.writeln('Hardware: ${android.hardware}');
        buffer.writeln('Product: ${android.product}');
        buffer.writeln('Supported ABIs: ${android.supportedAbis.join(', ')}');
        buffer.writeln('Is Physical Device: ${android.isPhysicalDevice}');
      } else if (Platform.isIOS) {
        final ios = await deviceInfo.iosInfo;
        buffer.writeln('Platform: iOS');
        buffer.writeln('Device: ${ios.utsname.machine}');
        buffer.writeln('Model: ${ios.model}');
        buffer.writeln('System Name: ${ios.systemName}');
        buffer.writeln('System Version: ${ios.systemVersion}');
        buffer.writeln('Device Name: ${ios.name}');
        buffer.writeln('Is Physical Device: ${ios.isPhysicalDevice}');
      }
    } catch (e) {
      buffer.writeln('Failed to get device info: $e');
    }
    buffer.writeln();

    buffer.writeln('--- Log Summary ---');
    buffer.writeln('Total Entries: ${_entries.length}');

    int errorCount = 0;
    int warnCount = 0;
    int infoCount = 0;
    int debugCount = 0;
    int goCount = 0;

    for (final entry in _entries) {
      switch (entry.level) {
        case 'ERROR':
        case 'FATAL':
          errorCount++;
          break;
        case 'WARN':
          warnCount++;
          break;
        case 'INFO':
          infoCount++;
          break;
        case 'DEBUG':
          debugCount++;
          break;
      }
      if (entry.isFromGo) goCount++;
    }

    buffer.writeln('Errors: $errorCount');
    buffer.writeln('Warnings: $warnCount');
    buffer.writeln('Info: $infoCount');
    buffer.writeln('Debug: $debugCount');
    buffer.writeln('From Go Backend: $goCount');
    buffer.writeln();

    buffer.writeln('=' * 60);
    buffer.writeln('LOG ENTRIES');
    buffer.writeln('=' * 60);
    buffer.writeln();

    for (final entry in _entries) {
      buffer.writeln(entry.toString());
    }

    return buffer.toString();
  }

  List<LogEntry> filter({String? level, String? tag, String? search}) {
    final tagLower = tag?.toLowerCase();
    final searchLower = search?.toLowerCase();

    return _entries.where((entry) {
      if (level != null && level != 'ALL' && entry.level != level) {
        return false;
      }
      if (tagLower != null && !entry.tag.toLowerCase().contains(tagLower)) {
        return false;
      }
      if (searchLower != null && searchLower.isNotEmpty) {
        return entry.message.toLowerCase().contains(searchLower) ||
            entry.tag.toLowerCase().contains(searchLower) ||
            (entry.error?.toLowerCase().contains(searchLower) ?? false);
      }
      return true;
    }).toList();
  }
}

class BufferedOutput extends LogOutput {
  final String tag;

  BufferedOutput(this.tag);

  @override
  void output(OutputEvent event) {
    if (kDebugMode) {
      for (final line in event.lines) {
        debugPrint(_truncateLogText(_redactSensitiveText(line)));
      }
    }

    final level = _levelToString(event.level);
    final message = _truncateLogText(
      _redactSensitiveText(event.lines.join('\n')),
    );

    LogBuffer().add(
      LogEntry(
        timestamp: DateTime.now(),
        level: level,
        tag: tag,
        message: message,
      ),
    );
  }

  String _levelToString(Level level) {
    switch (level) {
      case Level.debug:
        return 'DEBUG';
      case Level.info:
        return 'INFO';
      case Level.warning:
        return 'WARN';
      case Level.error:
        return 'ERROR';
      case Level.fatal:
        return 'FATAL';
      default:
        return 'LOG';
    }
  }
}

final log = Logger(
  printer: PrettyPrinter(
    methodCount: 0,
    errorMethodCount: 5,
    lineLength: 80,
    colors: true,
    printEmojis: false,
    dateTimeFormat: DateTimeFormat.none,
  ),
  level: Level.debug,
);

class AppLogger {
  final String _tag;
  late final Logger? _logger;

  AppLogger(this._tag) {
    if (kDebugMode) {
      _logger = Logger(
        printer: SimplePrinter(printTime: false, colors: false),
        output: BufferedOutput(_tag),
        level: Level.debug,
      );
    } else {
      _logger = null;
    }
  }

  void _addToBuffer(String level, String message, {String? error}) {
    if (!LogBuffer.loggingEnabled && level != 'ERROR' && level != 'FATAL') {
      return;
    }

    LogBuffer().add(
      LogEntry(
        timestamp: DateTime.now(),
        level: level,
        tag: _tag,
        message: message,
        error: error,
      ),
    );
  }

  void d(String message) {
    if (kDebugMode) {
      _logger?.d(message);
    } else {
      _addToBuffer('DEBUG', message);
    }
  }

  void i(String message) {
    if (kDebugMode) {
      _logger?.i(message);
    } else {
      _addToBuffer('INFO', message);
    }
  }

  void w(String message) {
    if (kDebugMode) {
      _logger?.w(message);
    } else {
      _addToBuffer('WARN', message);
    }
  }

  void e(String message, [Object? error, StackTrace? stackTrace]) {
    if (error != null) {
      _addToBuffer('ERROR', message, error: error.toString());
      if (kDebugMode) {
        debugPrint(
          '[$_tag] ERROR: ${_truncateLogText(_redactSensitiveText(message))} | ${_truncateLogText(_redactSensitiveText(error.toString()))}',
        );
        if (stackTrace != null) {
          debugPrint(stackTrace.toString());
        }
      }
    } else {
      if (kDebugMode) {
        _logger?.e(message);
      } else {
        _addToBuffer('ERROR', message);
      }
    }
  }
}
