import 'dart:async';

import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/contracts/auth.dart';
import 'package:smplayer/core/contracts/cancellation.dart';
import 'package:smplayer/core/contracts/library.dart';
import 'package:smplayer/core/contracts/server.dart';
import 'package:smplayer/core/domain/ports/emby_gateway.dart';
import 'package:smplayer/core/domain/ports/server_repository.dart';
import 'package:smplayer/core/domain/ports/session_repository.dart';
import 'package:smplayer/core/domain/usecases/media_usecases.dart';

class _FakeSessionRepository implements SessionRepository {
  _FakeSessionRepository(this.sessions);
  final List<SessionData> sessions;

  @override
  Future<List<SessionData>> listAll() async => sessions;

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

class _FakeServerRepository implements ServerRepository {
  _FakeServerRepository(this.servers);
  final List<EmbyServer> servers;

  @override
  Future<List<EmbyServer>> list() async => servers;

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

class _FakeEmbyGateway implements EmbyGateway {
  _FakeEmbyGateway(this.onGetLibraryItems);
  final Future<List<EmbyRawItem>> Function(AuthedRequestContext ctx)
  onGetLibraryItems;

  @override
  Future<List<EmbyRawItem>> getLibraryItems({
    required AuthedRequestContext ctx,
    String? parentId,
    String? includeItemTypes,
    List<String>? genreIds,
    String? searchTerm,
    String? anyProviderIdEquals,
    List<String>? personIds,
    String? fields,
    String? enableImageTypes,
    bool? groupProgramsBySeries,
    int? imageTypeLimit,
    int? startIndex,
    int? limit,
    bool? recursive,
    String? sortBy,
    SortOrder? sortOrder,
    String? filters,
    CancellationToken? cancelToken,
  }) => onGetLibraryItems(ctx);

  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

EmbyServer _server(String id) => EmbyServer(
  id: id,
  name: id,
  baseUrl: 'http://$id',
  createdAt: '2026-01-01T00:00:00Z',
  updatedAt: '2026-01-01T00:00:00Z',
);

SessionData _session(String serverId) => SessionData(
  serverId: serverId,
  token: 'token-$serverId',
  userId: 'user-$serverId',
  userName: serverId,
  createdAt: '2026-01-01T00:00:00Z',
);

EmbyRawItem _item(String id) => EmbyRawItem(Id: id, Name: id, Type: 'Movie');

TmdbAvailabilityUseCase _uc({
  required List<String> serverIds,
  required Future<List<EmbyRawItem>> Function(AuthedRequestContext ctx)
  onGetLibraryItems,
  Duration settleTimeout = const Duration(milliseconds: 100),
}) => TmdbAvailabilityUseCase(
  sessionRepository: _FakeSessionRepository(
    serverIds.map(_session).toList(growable: false),
  ),
  serverRepository: _FakeServerRepository(
    serverIds.map(_server).toList(growable: false),
  ),
  embyGateway: _FakeEmbyGateway(onGetLibraryItems),
  settleTimeout: settleTimeout,
);

void main() {
  const req = TmdbAvailabilityReq(tmdbId: '42', mediaType: 'movie');

  test('慢服务器挂起：settleTimeout 先发已完成快照，迟到命中追加补全', () async {
    final hang = Completer<List<EmbyRawItem>>();
    final uc = _uc(
      serverIds: ['fast', 'slow'],
      onGetLibraryItems: (ctx) => ctx.baseUrl == 'http://slow'
          ? hang.future
          : Future.value([_item('item-fast')]),
    );

    final emissions = <List<String>>[];
    final done = Completer<void>();
    uc
        .executeStream(req)
        .listen(
          (hits) => emissions.add(
            hits.map((h) => h.serverId).toList(growable: false),
          ),
          onDone: done.complete,
        );

    await Future<void>.delayed(const Duration(milliseconds: 300));
    expect(emissions, [
      ['fast'],
    ]);

    hang.complete([_item('item-slow')]);
    await done.future;
    expect(emissions, hasLength(2));
    expect(emissions.last, containsAll(['fast', 'slow']));
  });

  test('全部服务器先于 settleTimeout 返回：只发一次快照并立即关流', () async {
    final uc = _uc(
      serverIds: ['a', 'b'],
      onGetLibraryItems: (ctx) => Future.value([_item('item-${ctx.baseUrl}')]),
      settleTimeout: const Duration(seconds: 10),
    );

    final sw = Stopwatch()..start();
    final emissions = await uc.executeStream(req).toList();
    sw.stop();

    expect(sw.elapsed, lessThan(const Duration(seconds: 2)));
    expect(emissions, hasLength(1));
    expect(emissions.single.map((h) => h.serverId), containsAll(['a', 'b']));
  });

  test('迟到探测未命中（null）不重复发相同快照', () async {
    final hang = Completer<List<EmbyRawItem>>();
    final uc = _uc(
      serverIds: ['fast', 'slow'],
      onGetLibraryItems: (ctx) => ctx.baseUrl == 'http://slow'
          ? hang.future
          : Future.value([_item('item-fast')]),
    );

    final emissionsFuture = uc.executeStream(req).toList();
    await Future<void>.delayed(const Duration(milliseconds: 300));
    hang.complete(const []);
    final emissions = await emissionsFuture;

    expect(emissions, hasLength(1));
    expect(emissions.single.map((h) => h.serverId), ['fast']);
  });

  test('settleTimeout 默认 3 秒', () {
    final uc = TmdbAvailabilityUseCase(
      sessionRepository: _FakeSessionRepository(const []),
      serverRepository: _FakeServerRepository(const []),
      embyGateway: _FakeEmbyGateway((_) async => const []),
    );
    expect(uc.settleTimeout, const Duration(seconds: 3));
  });
}
