import 'package:commons_core/commons_core.dart';
import 'package:drift/drift.dart';

import '../db/chat_database.dart';

/// A conversation preview for the messages inbox.
class ChatSummary {
  const ChatSummary({
    required this.peerPubkey,
    required this.lastText,
    required this.lastAt,
  });

  final String peerPubkey;
  final String lastText;
  final DateTime lastAt;
}

/// Persists 1:1 chat history in the encrypted [ChatDatabase] (so no plaintext at
/// rest). Append is O(log n) — a single indexed insert, not a read-modify-write
/// of the whole conversation — and history is uncapped: nothing is silently
/// dropped. De-duplication is a DB invariant (the [Messages] unique key), so a
/// gift wrap re-delivered by a relay never piles up.
///
/// [accountScope] namespaces rows per social identity (empty = the original
/// identity's legacy scope). See [socialAccountScope].
class MessageStore {
  MessageStore(this._db, {String accountScope = ''}) : _scope = accountScope;

  final ChatDatabase _db;
  final String _scope;

  /// Messages exchanged with [peerPubkey], oldest first.
  Future<List<PrivateMessage>> history(String peerPubkey) async {
    final rows =
        await (_db.select(_db.messages)
              ..where(
                (m) =>
                    m.accountScope.equals(_scope) &
                    m.peerPubkey.equals(peerPubkey),
              )
              // Tie-break equal timestamps by insertion order (id) so history
              // is stable — matches the old append-order behaviour.
              ..orderBy([
                (m) => OrderingTerm(expression: m.sentAt),
                (m) => OrderingTerm(expression: m.id),
              ]))
            .get();
    return [for (final r in rows) _toMessage(r)];
  }

  /// Appends [message] to the conversation with [peerPubkey]. Idempotent: an
  /// identical message (same sender, timestamp and text) is dropped instead of
  /// duplicated, since a relay re-delivers stored gift wraps on every
  /// (re)subscribe. Returns true only when the message was newly stored.
  Future<bool> append(String peerPubkey, PrivateMessage message) {
    final at = message.at.millisecondsSinceEpoch;
    // SELECT-then-INSERT in a transaction so the dedup check and the write are
    // atomic; the unique key is the backstop. Both hit the conversation index.
    return _db.transaction(() async {
      final existing =
          await (_db.select(_db.messages)
                ..where(
                  (m) =>
                      m.accountScope.equals(_scope) &
                      m.peerPubkey.equals(peerPubkey) &
                      m.fromPubkey.equals(message.fromPubkey) &
                      m.sentAt.equals(at) &
                      m.body.equals(message.text),
                )
                ..limit(1))
              .getSingleOrNull();
      if (existing != null) return false;
      await _db
          .into(_db.messages)
          .insert(
            MessagesCompanion.insert(
              accountScope: _scope,
              peerPubkey: peerPubkey,
              fromPubkey: message.fromPubkey,
              body: message.text,
              sentAt: at,
            ),
          );
      return true;
    });
  }

  /// Conversations, most-recently-active first (for the messages inbox), each
  /// with its latest message. One indexed scan; the last message per peer is
  /// picked in Dart (peer counts are small).
  Future<List<ChatSummary>> conversations() async {
    final rows =
        await (_db.select(_db.messages)
              ..where((m) => m.accountScope.equals(_scope))
              ..orderBy([
                (m) =>
                    OrderingTerm(expression: m.sentAt, mode: OrderingMode.desc),
                (m) => OrderingTerm(expression: m.id, mode: OrderingMode.desc),
              ]))
            .get();
    final seen = <String>{};
    final out = <ChatSummary>[];
    for (final r in rows) {
      if (!seen.add(r.peerPubkey)) continue; // first (newest) per peer wins
      out.add(
        ChatSummary(
          peerPubkey: r.peerPubkey,
          lastText: r.body,
          lastAt: DateTime.fromMillisecondsSinceEpoch(r.sentAt),
        ),
      );
    }
    return out;
  }

  PrivateMessage _toMessage(Message r) => PrivateMessage(
    fromPubkey: r.fromPubkey,
    text: r.body,
    at: DateTime.fromMillisecondsSinceEpoch(r.sentAt),
  );
}
