// Version: 1.1.0 | Created: 2026-04-01
// 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 'dart:convert';

import 'package:drift/drift.dart';
import 'package:matrix/matrix.dart';
import 'package:riverpod_annotation/riverpod_annotation.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 {
    // Persist all rooms the client currently knows about.
    // We write every room on every sync — cheap upsert ensures we stay current.
    for (final room in _client.rooms) {
      await _db.upsertRoom(
        RoomsTableCompanion(
          id: Value(room.id),
          name: Value(room.getLocalizedDisplayname()),
          avatarUrl: Value(room.avatar?.toString()),
          lastMessage: Value(_lastMessagePreview(room)),
          lastActivityAt: Value(room.timeCreated.millisecondsSinceEpoch),
          unreadCount: Value(room.notificationCount),
          isDm: Value(room.isDirectChat),
        ),
      );
    }

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

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

      // timeline.events is List<MatrixEvent>? — may be null for rooms with
      // no new events in this sync batch.
      final events = timeline.events;
      if (events == null) continue;

      for (final event in events) {
        if (event.type != 'm.room.message') continue;

        // eventId, senderId, originServerTs are all non-null on MatrixEvent.
        await _db.upsertMessage(
          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),
            rawJson: Value(jsonEncode(event.toJson())),
          ),
        );
      }
    }
  }

  String? _lastMessagePreview(Room room) {
    final lastEvent = room.lastEvent;
    if (lastEvent == null) return null;
    return switch (lastEvent.type) {
      'm.room.message' => lastEvent.body,
      'm.room.encrypted' => 'Encrypted message',
      'm.sticker' => 'Sticker',
      _ => null,
    };
  }
}

/// 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;
}
