import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/contracts/danmaku.dart';
import 'package:smplayer/core/contracts/script_widget.dart';
import 'package:smplayer/core/contracts/server.dart';
import 'package:smplayer/core/domain/ports/danmaku_sources_store.dart';
import 'package:smplayer/core/domain/ports/script_widget_repository.dart';
import 'package:smplayer/core/domain/ports/server_repository.dart';
import 'package:smplayer/core/domain/ports/session_repository.dart';
import 'package:smplayer/core/contracts/auth.dart';
import 'package:smplayer/core/domain/usecases/backup_usecases.dart';

void main() {
  test('导出的备份包能被再次导入并保留脚本模块与订阅', () async {
    final timestamp = DateTime.utc(2026, 7, 13);
    final source = _MemorySetup.withScriptWidget(timestamp: timestamp);
    final export = ExportBackupUseCase(
      serverRepository: source.serverRepository,
      sessionRepository: source.sessionRepository,
      danmakuSourcesStore: source.danmakuSourcesStore,
      scriptWidgetRepository: source.scriptWidgetRepository,
      now: () => timestamp,
    );
    final backupResult = await export.execute();

    final destination = _MemorySetup.empty();
    final import = ImportBackupUseCase(
      serverRepository: destination.serverRepository,
      sessionRepository: destination.sessionRepository,
      danmakuSourcesStore: destination.danmakuSourcesStore,
      scriptWidgetRepository: destination.scriptWidgetRepository,
    );
    final importResult = await import.execute(backupResult.json);

    expect(backupResult.scriptWidgetCount, 1);
    expect(backupResult.scriptWidgetSubscriptionCount, 1);
    expect(importResult.scriptWidgetCount, 1);
    expect(importResult.scriptWidgetSubscriptionCount, 1);

    final restoredWidgets = await destination.scriptWidgetRepository
        .listWidgets();
    expect(restoredWidgets.single.manifest.id, 'demo-widget');
    expect(restoredWidgets.single.globalParams, <String, Object?>{
      'apiKey': 'secret-token',
    });

    final restoredSubscriptions = await destination.scriptWidgetRepository
        .listSubscriptions();
    expect(
      restoredSubscriptions.single.url,
      'https://example.com/catalog.fwd',
    );
  });
}

class _MemorySetup {
  final ServerRepository serverRepository;
  final SessionRepository sessionRepository;
  final DanmakuSourcesStore danmakuSourcesStore;
  final ScriptWidgetRepository scriptWidgetRepository;

  _MemorySetup({
    required this.serverRepository,
    required this.sessionRepository,
    required this.danmakuSourcesStore,
    required this.scriptWidgetRepository,
  });

  factory _MemorySetup.empty() {
    return _MemorySetup(
      serverRepository: _MemoryServerRepository(),
      sessionRepository: _MemorySessionRepository(),
      danmakuSourcesStore: _MemoryDanmakuSourcesStore(),
      scriptWidgetRepository: _MemoryScriptWidgetRepository(),
    );
  }

  factory _MemorySetup.withScriptWidget({required DateTime timestamp}) {
    final setup = _MemorySetup.empty();
    setup.scriptWidgetRepository.upsertWidget(
      InstalledScriptWidget(
        manifest: const ScriptWidgetManifest(
          id: 'demo-widget',
          title: 'Demo',
        ),
        scriptSource: 'const WidgetMetadata = { id: "demo-widget" };',
        globalParams: const <String, Object?>{'apiKey': 'secret-token'},
        installedAt: timestamp,
        updatedAt: timestamp,
      ),
    );
    setup.scriptWidgetRepository.upsertSubscription(
      ScriptWidgetSubscription(
        url: 'https://example.com/catalog.fwd',
        catalog: const ScriptWidgetCatalog(title: '示例订阅'),
        refreshedAt: timestamp,
      ),
    );
    return setup;
  }
}

class _MemoryServerRepository implements ServerRepository {
  final List<EmbyServer> _servers = <EmbyServer>[];

  @override
  Future<List<EmbyServer>> list() async =>
      List<EmbyServer>.unmodifiable(_servers);

  @override
  Future<EmbyServer?> findById(String id) async {
    for (final server in _servers) {
      if (server.id == id) return server;
    }
    return null;
  }

  @override
  Future<EmbyServer?> findByBaseUrl(String baseUrl) async {
    for (final server in _servers) {
      if (server.baseUrl == baseUrl) return server;
    }
    return null;
  }

  @override
  Future<EmbyServer> create(EmbyServer server) async {
    _servers.add(server);
    return server;
  }

  @override
  Future<EmbyServer> update(EmbyServer server) async {
    final existingIndex = _servers.indexWhere(
      (candidate) => candidate.id == server.id,
    );
    if (existingIndex < 0) {
      _servers.add(server);
    } else {
      _servers[existingIndex] = server;
    }
    return server;
  }

  @override
  Future<void> deleteById(String id) async {
    _servers.removeWhere((server) => server.id == id);
  }

  @override
  Future<void> touchConnectedAt(String id, String updatedAt) async {}

  @override
  Future<void> replaceAll(List<EmbyServer> servers) async {
    _servers
      ..clear()
      ..addAll(servers);
  }
}

class _MemorySessionRepository implements SessionRepository {
  final Map<String, SessionData> _sessionsByServerId = <String, SessionData>{};
  String? _activeServerId;

  @override
  Future<SessionData> set(SessionData session) async {
    _sessionsByServerId[session.serverId] = session;
    return session;
  }

  @override
  Future<void> setMany(List<SessionData> sessions) async {
    for (final session in sessions) {
      _sessionsByServerId[session.serverId] = session;
    }
  }

  @override
  Future<SessionData?> getByServerId(String serverId) async {
    return _sessionsByServerId[serverId];
  }

  @override
  Future<SessionData?> getActive() async {
    final activeId = _activeServerId;
    if (activeId == null) return null;
    return _sessionsByServerId[activeId];
  }

  @override
  Future<String?> getActiveServerId() async => _activeServerId;

  @override
  Future<List<SessionData>> listAll() async =>
      List<SessionData>.unmodifiable(_sessionsByServerId.values);

  @override
  Future<void> setActive(String serverId) async {
    _activeServerId = serverId;
  }

  @override
  Future<void> clear([String? serverId]) async {
    if (serverId == null) {
      _sessionsByServerId.clear();
      _activeServerId = null;
      return;
    }
    _sessionsByServerId.remove(serverId);
    if (_activeServerId == serverId) _activeServerId = null;
  }

  @override
  Future<void> replaceAll(
    List<SessionData> sessions, {
    String? activeServerId,
  }) async {
    _sessionsByServerId
      ..clear()
      ..addEntries(
        sessions.map((session) => MapEntry(session.serverId, session)),
      );
    _activeServerId = activeServerId;
  }
}

class _MemoryDanmakuSourcesStore implements DanmakuSourcesStore {
  List<DanmakuSource> _sources = <DanmakuSource>[];

  @override
  Future<List<DanmakuSource>> list() async =>
      List<DanmakuSource>.from(_sources);

  @override
  Future<void> replaceAll(List<DanmakuSource> sources) async {
    _sources = List<DanmakuSource>.from(sources);
  }
}

class _MemoryScriptWidgetRepository implements ScriptWidgetRepository {
  final Map<String, InstalledScriptWidget> _widgets =
      <String, InstalledScriptWidget>{};
  final Map<String, ScriptWidgetSubscription> _subscriptions =
      <String, ScriptWidgetSubscription>{};

  @override
  Future<List<InstalledScriptWidget>> listWidgets() async =>
      _widgets.values.toList(growable: false);

  @override
  Future<InstalledScriptWidget?> findWidget(String widgetId) async {
    return _widgets[widgetId];
  }

  @override
  Future<void> upsertWidget(InstalledScriptWidget widget) async {
    _widgets[widget.manifest.id] = widget;
  }

  @override
  Future<void> deleteWidget(String widgetId) async {
    _widgets.remove(widgetId);
  }

  @override
  Future<List<ScriptWidgetSubscription>> listSubscriptions() async =>
      _subscriptions.values.toList(growable: false);

  @override
  Future<void> upsertSubscription(ScriptWidgetSubscription subscription) async {
    _subscriptions[subscription.url] = subscription;
  }

  @override
  Future<void> replaceAllWidgets(List<InstalledScriptWidget> widgets) async {
    _widgets
      ..clear()
      ..addEntries(
        widgets.map((widget) => MapEntry(widget.manifest.id, widget)),
      );
  }

  @override
  Future<void> replaceAllSubscriptions(
    List<ScriptWidgetSubscription> subscriptions,
  ) async {
    _subscriptions
      ..clear()
      ..addEntries(
        subscriptions.map(
          (subscription) => MapEntry(subscription.url, subscription),
        ),
      );
  }
}
