import 'package:sentinel_shield/core/offline/offline_database.dart';
import 'package:sentinel_shield/core/offline/offline_location_record.dart';
import 'package:sqflite/sqflite.dart';

class OfflineLocationCache {
  const OfflineLocationCache(this._database);

  final OfflineDatabase _database;

  Future<void> enqueue(OfflineLocationRecord location) async {
    final Database database = await _database.instance;
    await database.insert(
      'pending_locations',
      location.toDatabase(),
      conflictAlgorithm: ConflictAlgorithm.ignore,
    );
  }

  Future<List<OfflineLocationRecord>> pendingBatch({
    int limit = 100,
    DateTime? now,
  }) async {
    final Database database = await _database.instance;
    final String instant = (now ?? DateTime.now()).toUtc().toIso8601String();
    final List<Map<String, Object?>> rows = await database.query(
      'pending_locations',
      where: 'next_attempt_at IS NULL OR next_attempt_at <= ?',
      whereArgs: <Object>[instant],
      orderBy: 'recorded_at ASC',
      limit: limit.clamp(1, 500),
    );

    return rows.map(OfflineLocationRecord.fromDatabase).toList(growable: false);
  }

  Future<void> removeUploaded(Iterable<String> clientIds) async {
    final List<String> ids = clientIds.toSet().toList(growable: false);
    if (ids.isEmpty) return;

    final Database database = await _database.instance;
    final String placeholders = List<String>.filled(ids.length, '?').join(',');
    await database.delete(
      'pending_locations',
      where: 'client_id IN ($placeholders)',
      whereArgs: ids,
    );
  }

  Future<void> markFailed(
    Iterable<OfflineLocationRecord> locations, {
    DateTime? now,
  }) async {
    final Database database = await _database.instance;
    final DateTime instant = (now ?? DateTime.now()).toUtc();
    await database.transaction((Transaction transaction) async {
      for (final OfflineLocationRecord location in locations) {
        final int attempts = location.attempts + 1;
        final int backoffMinutes = (1 << attempts.clamp(0, 8)).clamp(2, 360);
        await transaction.update(
          'pending_locations',
          <String, Object?>{
            'attempts': attempts,
            'next_attempt_at': instant
                .add(Duration(minutes: backoffMinutes))
                .toIso8601String(),
          },
          where: 'client_id = ?',
          whereArgs: <Object>[location.clientId],
        );
      }
    });
  }

  Future<int> count() async {
    final Database database = await _database.instance;
    return Sqflite.firstIntValue(
          await database.rawQuery('SELECT COUNT(*) FROM pending_locations'),
        ) ??
        0;
  }

  Future<void> clear() async {
    final Database database = await _database.instance;
    await database.delete('pending_locations');
  }
}
