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, {this.activeServerId});
  final List<SessionData> sessions;
  final String? activeServerId;

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

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

  @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 _CountingEmbyGateway implements EmbyGateway {
  _CountingEmbyGateway({this.itemsToReturn = const []});
  List<EmbyRawItem> itemsToReturn;
  int libraryQueryCount = 0;

  @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,
  }) async {
    libraryQueryCount++;
    return itemsToReturn;
  }

  @override
  Future<EmbyRawItem> getItemDetail(
    AuthedRequestContext ctx,
    String itemId,
  ) async => itemsToReturn.firstWhere((it) => it.Id == itemId);

  @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',
);


Future<List<CrossServerSourceCard>> _collectCards(
  CrossServerSearchUseCase useCase,
  CrossServerSearchReq request,
) async {
  final events = await useCase.executeStream(request).toList();
  return events.isEmpty ? const [] : events.last;
}

void main() {
  const movieReq = CrossServerSearchReq(
    anchorType: 'Movie',
    itemName: 'Movie One',
    providerIds: {'Tmdb': '42'},
  );

  ({CrossServerSearchUseCase uc, _CountingEmbyGateway gateway}) build({
    List<EmbyRawItem> remoteItems = const [],
  }) {
    final gateway = _CountingEmbyGateway(itemsToReturn: remoteItems);
    final uc = CrossServerSearchUseCase(
      sessionRepository: _FakeSessionRepository([
        _session('active'),
        _session('remote'),
      ], activeServerId: 'active'),
      serverRepository: _FakeServerRepository([
        _server('active'),
        _server('remote'),
      ]),
      embyGateway: gateway,
    );
    return (uc: uc, gateway: gateway);
  }

  group('CrossServerSearchUseCase 空服务器跳过（streak+TTL）', () {
    test('连续 3 次空结果后，该服务器被跳过不再探测', () async {
      final (:uc, :gateway) = build();

      for (var round = 1; round <= 3; round++) {
        final cards = await _collectCards(uc, movieReq);
        expect(cards, isEmpty, reason: 'round $round 应为空结果');
      }
      final queriesAfterThreeRounds = gateway.libraryQueryCount;
      expect(queriesAfterThreeRounds, greaterThan(0));

      final cards = await _collectCards(uc, movieReq);
      expect(cards, isEmpty);
      expect(
        gateway.libraryQueryCount,
        queriesAfterThreeRounds,
        reason: '第 4 轮应命中 streak 跳过，网关不应再被探测',
      );
    });

    test('未达阈值（2 次空）时仍继续探测', () async {
      final (:uc, :gateway) = build();

      await _collectCards(uc, movieReq);
      await _collectCards(uc, movieReq);
      final queriesAfterTwoRounds = gateway.libraryQueryCount;

      await _collectCards(uc, movieReq);
      expect(gateway.libraryQueryCount, greaterThan(queriesAfterTwoRounds));
    });

    test('命中结果会清空 streak 并产出多版本 cards', () async {
      final movie = EmbyRawItem.fromJson(const {
        'Id': 'm1',
        'Name': 'Movie One',
        'Type': 'Movie',
        'MediaSources': [
          {'Id': 'ms1', 'Name': '1080p'},
          {'Id': 'ms2', 'Name': '4K'},
        ],
      });
      final (:uc, :gateway) = build(remoteItems: [movie]);

      final cards = await _collectCards(uc, movieReq);
      expect(cards, hasLength(2));
      expect(cards.map((c) => c.mediaSourceId), containsAll(['ms1', 'ms2']));
      expect(cards.every((c) => c.serverId == 'remote'), isTrue);

      gateway.itemsToReturn = const [];
      final baseline = gateway.libraryQueryCount;
      await _collectCards(uc, movieReq);
      expect(gateway.libraryQueryCount, greaterThan(baseline));
    });
  });
}
