import 'dart:async';

import 'package:commons_core/commons_core.dart';

import '../security/secret_store.dart';
import 'message_store.dart';

/// Tracks which private messages are still unread, so the UI can show a badge.
///
/// There is no read/unread flag on a message; instead we keep a per-peer
/// "last read at" timestamp in the keystore (just a timestamp and a pubkey — no
/// message text, so it honours "no plaintext at rest"). A conversation's unread
/// count is simply the peer's messages newer than that mark. Reading history
/// from the [MessageStore] keeps a single source of truth.
///
/// Only inbound messages count: a message whose author is the peer. Your own
/// sent messages (stored with your pubkey) are never counted.
///
/// [activePeer] is the chat currently on screen. A message that arrives for it
/// is treated as already read (you're looking at it), and the inbox listener
/// also skips its notification — so the chat you're in never badges or beeps.
class UnreadService {
  UnreadService(this._store, this._secrets);

  final MessageStore _store;
  final SecretStore _secrets;

  final _changes = StreamController<void>.broadcast();

  /// The peer whose chat is currently open, or null. Set by [ChatScreen].
  String? activePeer;

  static const _prefix = 'tane.social.unread.';

  String _key(String peer) => '$_prefix$peer';

  /// Fires (no payload) whenever an unread count may have changed — on a new
  /// inbound message and on [markRead]. Broadcast, so several badges can listen.
  Stream<void> get changes => _changes.stream;

  /// Records that a new [message] for [peer] was just persisted. If its chat is
  /// open it is marked read immediately; otherwise badges are asked to refresh.
  Future<void> onMessageReceived(String peer, PrivateMessage message) async {
    if (peer == activePeer) {
      await markRead(peer);
    } else {
      _fire();
    }
  }

  /// Marks the conversation with [peer] read up to its latest message, so its
  /// unread count drops to zero. Called when the chat opens (and when a message
  /// arrives while it's open).
  Future<void> markRead(String peer) async {
    final history = await _store.history(peer);
    final upTo = history.isEmpty
        ? DateTime.now()
        : history
            .map((m) => m.at)
            .reduce((a, b) => a.isAfter(b) ? a : b);
    await _secrets.write(_key(peer), '${upTo.millisecondsSinceEpoch}');
    _fire();
  }

  /// Unread inbound messages from [peer] (those newer than its last-read mark).
  Future<int> unreadCount(String peer) async {
    final lastRead = await _lastRead(peer);
    final history = await _store.history(peer);
    return history
        .where((m) => m.fromPubkey == peer && m.at.isAfter(lastRead))
        .length;
  }

  /// Unread messages across every conversation (the number for the app badge).
  Future<int> totalUnreadCount() async {
    var total = 0;
    for (final c in await _store.conversations()) {
      total += await unreadCount(c.peerPubkey);
    }
    return total;
  }

  Future<DateTime> _lastRead(String peer) async {
    final raw = await _secrets.read(_key(peer));
    final ms = raw == null ? null : int.tryParse(raw);
    return DateTime.fromMillisecondsSinceEpoch(ms ?? 0);
  }

  void _fire() {
    if (!_changes.isClosed) _changes.add(null);
  }

  Future<void> dispose() async {
    if (!_changes.isClosed) await _changes.close();
  }
}
