// Version: 1.2.0 | Created: 2026-04-01 | Updated: 2026-04-02
// SyncPersistenceService — listens to the Matrix sync stream and writes room
// and message data into the Drift database.
//
// Wired as a keepAlive provider so it starts after login and runs for the
// session lifetime. It does NOT block the UI sync loop — writes are fire-and-
// forget on the Drift isolate.

import 'dart:async';

import 'package:drift/drift.dart';
import 'package:matrix/matrix.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../shared/utils/mxc_url.dart';
import '../../shared/utils/room_preview.dart';
import '../network/matrix_client.dart';
import 'database.dart';

part 'sync_persistence_service.g.dart';

/// Starts and holds the background sync persistence listener.
/// Call [start] after successful login.
class SyncPersistenceService {
  SyncPersistenceService({required Client client, required AppDatabase db})
    : _client = client,
      _db = db;

  final Client _client;
  final AppDatabase _db;
  StreamSubscription<SyncUpdate>? _subscription;

  void start() {
    _subscription?.cancel();
    _subscription = _client.onSync.stream.listen(_onSync);
  }

  void stop() {
    _subscription?.cancel();
    _subscription = null;
  }

  Future<void> _onSync(SyncUpdate update) async {
    // Collect room IDs that changed in this sync batch.
    final changedIds = <String>{
      ...?update.rooms?.join?.keys,
      ...?update.rooms?.invite?.keys,
      ...?update.rooms?.leave?.keys,
    };

    // Only upsert rooms that actually changed — avoids O(n) writes.
    if (changedIds.isNotEmpty) {
      final roomCompanions = <RoomsTableCompanion>[];
      for (final roomId in changedIds) {
        final room = _client.getRoomById(roomId);
        if (room == null) continue;
        roomCompanions.add(
          RoomsTableCompanion(
            id: Value(room.id),
            name: Value(room.getLocalizedDisplayname()),
            avatarUrl: Value(resolveMxcUrl(_client, room.avatar)),
            lastMessage: Value(lastMessagePreview(room)),
            lastActivityAt: Value(
              (room.lastEvent?.originServerTs ?? room.timeCreated)
                  .millisecondsSinceEpoch,
            ),
            unreadCount: Value(room.notificationCount),
            isDm: Value(room.isDirectChat),
          ),
        );
      }
      for (final companion in roomCompanions) {
        await _db.upsertRoom(companion);
      }
    }

    // Persist new events from joined rooms in this sync batch.
    final joinedRooms = update.rooms?.join;
    if (joinedRooms == null) return;

    final messageCompanions = <MessagesTableCompanion>[];

    for (final entry in joinedRooms.entries) {
      final roomId = entry.key;
      final timeline = entry.value.timeline;
      if (timeline == null) continue;

      final events = timeline.events;
      if (events == null) continue;

      for (final event in events) {
        if (event.type != EventTypes.Message) continue;

        messageCompanions.add(
          MessagesTableCompanion(
            id: Value(event.eventId),
            roomId: Value(roomId),
            senderId: Value(event.senderId),
            body: Value(event.content['body'] as String?),
            type: Value(event.content['msgtype'] as String? ?? 'unknown'),
            timestamp: Value(event.originServerTs.millisecondsSinceEpoch),
          ),
        );
      }
    }

    // Batch insert all messages in a single transaction.
    if (messageCompanions.isNotEmpty) {
      await _db.upsertMessages(messageCompanions);
    }
  }
}

/// keepAlive provider — the service stays alive for the session lifetime.
@Riverpod(keepAlive: true)
SyncPersistenceService syncPersistenceService(Ref ref) {
  final client = ref.watch(matrixClientProvider);
  final db = ref.watch(appDatabaseProvider);
  final service = SyncPersistenceService(client: client, db: db);
  ref.onDispose(service.stop);
  return service;
}
