import 'dart:async';
import 'dart:math';
import 'package:uuid/uuid.dart';
import 'package:flutter/material.dart';
import 'models/telemetry_event.dart';
import 'models/device_context.dart';
import 'models/telemetry_config.dart';
import 'collectors/device_info_collector.dart';
import 'storage/telemetry_storage.dart';
import 'uploader/telemetry_uploader.dart';
import 'session/session_manager.dart';
import 'presence/heartbeat_service.dart';
import 'presence/presence_config.dart';

class TelemetryService {
  static TelemetryService? _instance;
  TelemetryService._();
  factory TelemetryService() {
    _instance ??= TelemetryService._();
    return _instance!;
  }

  final _storage = TelemetryStorage();
  late TelemetryUploader _uploader;

  DeviceContext? _deviceContext;

  late String _installId;
  String get installId => _installId;

  String? _userId;
  String? get userId => _userId;

  String? _accessToken;

  bool _isInitialized = false;
  bool get isInitialized => _isInitialized;

  late SessionManager _sessionManager;
  late HeartbeatService _heartbeatService;

  Timer? _configSyncTimer;

  Future<void> initialize({
    required String apiBaseUrl,
    required BuildContext context,
    String? userId,
    Duration configSyncInterval = const Duration(hours: 1),
    PresenceConfig? presenceConfig,
  }) async {
    if (_isInitialized) return;

    await _storage.init();
    await _initInstallId();
    await TelemetryConfig().loadUserOptIn();

    TelemetryConfig().syncFromRemote(apiBaseUrl).catchError((e) {
      debugPrint('[Telemetry] Remote config sync failed: $e');
    });

    _deviceContext = await DeviceInfoCollector().collect(context);
    await _storage.saveDeviceContext(_deviceContext!.toJson());

    _userId = userId;

    _uploader = TelemetryUploader(
      apiBaseUrl: apiBaseUrl,
      storage: _storage,
      getAuthHeaders: _getAuthHeaders,
    );

    if (TelemetryConfig().globalEnabled) {
      _uploader.startPeriodicUpload();
    }

    _configSyncTimer = Timer.periodic(configSyncInterval, (_) async {
      await TelemetryConfig().syncFromRemote(apiBaseUrl);
      if (TelemetryConfig().globalEnabled) {
        _uploader.startPeriodicUpload();
      } else {
        _uploader.stopPeriodicUpload();
      }
      if (TelemetryConfig().presenceConfig != null) {
        _heartbeatService.updateConfig(TelemetryConfig().presenceConfig!);
      }
    });

    _sessionManager = SessionManager();
    _sessionManager.initialize(this);

    _heartbeatService = HeartbeatService();
    _heartbeatService.initialize(
      apiBaseUrl: apiBaseUrl,
      config: presenceConfig ?? TelemetryConfig().presenceConfig,
      getInstallId: () => _installId,
      getUserId: () => _userId,
      getAppVersion: () => _deviceContext?.appVersion ?? 'unknown',
      getAuthHeaders: _getAuthHeaders,
    );

    _isInitialized = true;
    debugPrint('[Telemetry] Initialized | installId: $_installId | userId: $_userId');
  }

  Future<void> _initInstallId() async {
    final storedId = _storage.getInstallId();
    if (storedId != null) {
      _installId = storedId;
    } else {
      _installId = const Uuid().v4();
      await _storage.saveInstallId(_installId);
    }
  }

  Map<String, String> _getAuthHeaders() {
    if (_accessToken == null) return {};
    return {'Authorization': 'Bearer $_accessToken'};
  }

  void setAccessToken(String? token) => _accessToken = token;
  void clearAccessToken() => _accessToken = null;

  void setUserId(String? userId) {
    _userId = userId;
    debugPrint('[Telemetry] userId set: $userId');
  }

  void clearUserId() {
    _userId = null;
    debugPrint('[Telemetry] userId cleared');
  }

  void logEvent(
    String eventName, {
    EventType type = EventType.userAction,
    EventLevel level = EventLevel.info,
    Map<String, dynamic>? properties,
  }) {
    if (!_isInitialized) return;
    if (!TelemetryConfig().shouldLog(type, eventName)) return;

    if (_needsSampling(type)) {
      if (Random().nextDouble() > TelemetryConfig().samplingRate) return;
    }

    final deviceProps = _deviceContext != null
        ? {
            'device_brand': _deviceContext!.brand,
            'device_model': _deviceContext!.model,
            'device_os': _deviceContext!.osVersion,
            'app_version': _deviceContext!.appVersion,
            'locale': _deviceContext!.locale,
          }
        : <String, dynamic>{};

    final event = TelemetryEvent(
      eventId: const Uuid().v4(),
      type: type,
      level: level,
      name: eventName,
      properties: {...deviceProps, ...?properties},
      timestamp: DateTime.now(),
      userId: _userId,
      sessionId: _sessionManager.currentSessionId,
      installId: _installId,
      deviceContextId: _deviceContext?.androidId ?? '',
    );

    _storage.enqueueEvent(event);
    _uploader.uploadIfNeeded();
  }

  bool _needsSampling(EventType type) =>
      type != EventType.error && type != EventType.crash && type != EventType.session;

  void logPageView(String pageName, {Map<String, dynamic>? extra}) {
    logEvent('page_view', type: EventType.pageView, properties: {'page': pageName, ...?extra});
  }

  void logUserAction(String action, {Map<String, dynamic>? properties}) {
    logEvent(action, type: EventType.userAction, properties: properties);
  }

  void logError(String errorMessage, {Object? error, StackTrace? stackTrace, Map<String, dynamic>? extra}) {
    logEvent('error_occurred', type: EventType.error, level: EventLevel.error, properties: {
      'message': errorMessage,
      'error': error?.toString(),
      'stack_trace': stackTrace?.toString(),
      ...?extra,
    });
  }

  Future<void> flushOnBackground() async {
    await _uploader.uploadBatch(batchSize: 50);
  }

  Future<void> pauseForLogout() async {
    _uploader.stopPeriodicUpload();
    await _storage.clearEventQueue();
    _userId = null;
  }

  void resumeAfterLogin() {
    if (TelemetryConfig().globalEnabled) {
      _uploader.startPeriodicUpload();
    }
  }

  String? get currentSessionId => _sessionManager.currentSessionId;
  int get sessionDurationSeconds => _sessionManager.sessionDurationSeconds;
  bool get isHeartbeatRunning => _heartbeatService.isRunning;
  int get heartbeatCount => _heartbeatService.heartbeatCount;
  DeviceContext? get deviceContext => _deviceContext;

  Future<void> dispose() async {
    _configSyncTimer?.cancel();
    _sessionManager.dispose();
    _heartbeatService.dispose();
    await _uploader.forceUploadAll();
    _isInitialized = false;
  }

  static void reset() => _instance = null;
}
