import 'dart:async';

import 'package:dio/dio.dart';
import 'package:recording_tool/core/logging/app_logger.dart';
import 'package:recording_tool/core/network/network_monitor.dart';
import 'package:recording_tool/core/network/offline_queue/offline_queue_state.dart';
import 'package:recording_tool/core/network/offline_queue/offline_queue_storage.dart';
import 'package:recording_tool/core/network/offline_queue/offline_request.dart';

class OfflineQueueManager {
  OfflineQueueManager({
    required this.dio,
    required this.monitor,
    required this.storage,
  });

  final Dio dio;
  final NetworkMonitor monitor;
  final OfflineQueueStorage storage;

  final _queue = <OfflineRequest>[];
  final _dead = <OfflineRequest>[];
  final _stateController = StreamController<OfflineQueueState>.broadcast();

  StreamSubscription<dynamic>? _networkSubscription;
  Timer? _replayDebounceTimer;
  bool _replaying = false;

  Stream<OfflineQueueState> get stateStream => _stateController.stream;

  Future<void> init() async {
    _queue.addAll(await storage.loadQueue());
    _dead.addAll(await storage.loadDead());
    _sort();
    _emit();

    _networkSubscription = monitor.stream.listen((state) {
      if (state.isOffline) {
        _replayDebounceTimer?.cancel();
        return;
      }

      if (state.isOnline) {
        _replayDebounceTimer?.cancel();
        _replayDebounceTimer = Timer(const Duration(seconds: 2), replay);
      }
    });
  }

  Future<void> enqueue(OfflineRequest request) async {
    if (_isDuplicate(request)) return;

    _queue.add(request);
    _sort();
    await storage.saveQueue(_queue);
    _emit();
  }

  Future<void> replay() async {
    if (!monitor.current.isOnline || _replaying || _queue.isEmpty) return;

    _replaying = true;
    _emit();

    try {
      while (_queue.isNotEmpty && monitor.current.isOnline) {
        final request = _queue.first;
        try {
          await dio.request<dynamic>(
            request.path,
            data: request.body,
            queryParameters: request.query,
            options: Options(
              method: request.method,
              headers: request.headers,
              extra: const {'replay': true, 'offline': false},
            ),
          );
          _queue.removeAt(0);
          await storage.saveQueue(_queue);
          _emit();
        } catch (error, stackTrace) {
          AppLogger.debug(
            'Offline replay failed: ${request.method} ${request.path}',
            error: error,
            stackTrace: stackTrace,
          );
          final updated = request.copyWith(retryCount: request.retryCount + 1);
          _queue.removeAt(0);
          if (updated.isDead) {
            _dead.add(updated);
            await storage.saveDead(_dead);
          } else {
            _queue.add(updated);
            _sort();
          }
          await storage.saveQueue(_queue);
          _emit();
          break;
        }
      }
    } finally {
      _replaying = false;
      _emit();
    }
  }

  bool _isDuplicate(OfflineRequest request) {
    return _queue.any(
      (item) =>
          (request.idempotencyKey.isNotEmpty &&
              item.idempotencyKey == request.idempotencyKey) ||
          (item.method == request.method && item.path == request.path),
    );
  }

  void _sort() {
    _queue.sort((a, b) {
      final priority = a.priority.index.compareTo(b.priority.index);
      return priority != 0
          ? priority
          : a.category.index.compareTo(b.category.index);
    });
  }

  void _emit() {
    _stateController.add(
      OfflineQueueState(
        pending: _queue.length,
        dead: _dead.length,
        replaying: _replaying,
      ),
    );
  }

  void dispose() {
    _replayDebounceTimer?.cancel();
    _networkSubscription?.cancel();
    _stateController.close();
  }
}
