import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/contracts/library.dart';
import 'package:smplayer/features/aggregated/aggregated_grouping.dart';

GlobalSearchServerResult _server(
  String id,
  String name,
  List<EmbyRawItem> items,
) => GlobalSearchServerResult(
  serverId: id,
  serverName: name,
  serverUrl: 'http://$id',
  token: 'tok-$id',
  userId: 'user-$id',
  items: items,
);

EmbyRawItem _item(
  String id,
  String name, {
  String? type,
  Map<String, dynamic>? providerIds,
  String? lastPlayedDate,
  int? playbackPositionTicks,
  int? runtimeTicks,
  bool? played,
}) => EmbyRawItem(
  Id: id,
  Name: name,
  Type: type,
  RunTimeTicks: runtimeTicks,
  UserData: playbackPositionTicks != null || played != null
      ? EmbyRawUserData(
          PlaybackPositionTicks: playbackPositionTicks,
          Played: played,
        )
      : null,
  extra: {
    if (providerIds != null) 'ProviderIds': providerIds,
    if (lastPlayedDate != null) 'UserData': {'LastPlayedDate': lastPlayedDate},
  },
);

void main() {
  group('flatten – default behavior (dedup off)', () {
    test('returns all items from all servers', () {
      final results = [
        _server('s1', 'S1', [_item('a', 'Movie A')]),
        _server('s2', 'S2', [_item('b', 'Movie B')]),
      ];
      final entries = flatten(results);
      expect(entries.length, 2);
    });

    test('respects serverFilter', () {
      final results = [
        _server('s1', 'S1', [_item('a', 'A')]),
        _server('s2', 'S2', [_item('b', 'B')]),
      ];
      final entries = flatten(results, serverFilter: 's1');
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's1');
    });
  });

  group('flatten – dedup on, serverFilter null', () {
    test('collapses same TMDB Movie to newest LastPlayedDate', () {
      final results = [
        _server('s1', 'S1', [
          _item(
            'a',
            'Movie',
            type: 'Movie',
            providerIds: {'Tmdb': '100'},
            lastPlayedDate: '2026-06-20T10:00:00Z',
          ),
        ]),
        _server('s2', 'S2', [
          _item(
            'b',
            'Movie',
            type: 'Movie',
            providerIds: {'Tmdb': '100'},
            lastPlayedDate: '2026-06-24T15:00:00Z',
          ),
        ]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's2');
      expect(entries.first.sources.map((source) => source.source.serverId), [
        's2',
        's1',
      ]);
    });

    test('preserves each server item and its independent playback state', () {
      final results = [
        _server('s1', 'S1', [
          _item(
            'a',
            'Movie',
            type: 'Movie',
            providerIds: {'Tmdb': '100'},
            lastPlayedDate: '2026-06-20T10:00:00Z',
            playbackPositionTicks: 100,
            runtimeTicks: 1000,
          ),
        ]),
        _server('s2', 'S2', [
          _item(
            'b',
            'Movie',
            type: 'Movie',
            providerIds: {'Tmdb': '100'},
            lastPlayedDate: '2026-06-24T15:00:00Z',
            playbackPositionTicks: 800,
            runtimeTicks: 1000,
          ),
        ]),
      ];

      final entry = flatten(results, deduplicateAcrossServers: true).single;

      expect(entry.sources, hasLength(2));
      expect(entry.sources[0].item.UserData?.PlaybackPositionTicks, 800);
      expect(entry.sources[1].item.UserData?.PlaybackPositionTicks, 100);
      expect(entry.sources[0].item.RunTimeTicks, 1000);
      expect(entry.sources[1].item.RunTimeTicks, 1000);
    });

    test('keeps input order when sources have equal LastPlayedDate', () {
      final results = [
        _server('s1', 'S1', [
          _item(
            'a',
            'Movie',
            type: 'Movie',
            providerIds: {'Tmdb': '100'},
            lastPlayedDate: '2026-06-24T15:00:00Z',
          ),
        ]),
        _server('s2', 'S2', [
          _item(
            'b',
            'Movie',
            type: 'Movie',
            providerIds: {'Tmdb': '100'},
            lastPlayedDate: '2026-06-24T15:00:00Z',
          ),
        ]),
      ];

      final entry = flatten(results, deduplicateAcrossServers: true).single;

      expect(entry.sources.map((source) => source.source.serverId), [
        's1',
        's2',
      ]);
    });
  });

  group('flatten – dedup on + serverFilter non-null', () {
    test('skips dedup when server filter is active', () {
      final results = [
        _server('s1', 'S1', [
          _item('a', 'Movie', type: 'Movie', providerIds: {'Tmdb': '100'}),
        ]),
        _server('s2', 'S2', [
          _item('b', 'Movie', type: 'Movie', providerIds: {'Tmdb': '100'}),
        ]),
      ];
      final entries = flatten(
        results,
        serverFilter: 's1',
        deduplicateAcrossServers: true,
      );
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's1');
      expect(entries.first.sources, hasLength(1));
    });
  });

  group('no ProviderIds – never deduped', () {
    test('items without ProviderIds stay separate', () {
      final results = [
        _server('s1', 'S1', [_item('a', 'Same Name', type: 'Movie')]),
        _server('s2', 'S2', [_item('b', 'Same Name', type: 'Movie')]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 2);
    });
  });

  group('Type namespace isolation', () {
    test('Movie and Series with same tmdb id stay separate', () {
      final results = [
        _server('s1', 'S1', [
          _item('a', 'Title', type: 'Movie', providerIds: {'Tmdb': '100'}),
        ]),
        _server('s2', 'S2', [
          _item('b', 'Title', type: 'Series', providerIds: {'Tmdb': '100'}),
        ]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 2);
    });
  });

  group('ProviderIds key case insensitivity', () {
    test('Tmdb vs tmdb vs TMDB all match', () {
      final results = [
        _server('s1', 'S1', [
          _item(
            'a',
            'M',
            type: 'Movie',
            providerIds: {'Tmdb': '99'},
            lastPlayedDate: '2026-06-20T00:00:00Z',
          ),
        ]),
        _server('s2', 'S2', [
          _item(
            'b',
            'M',
            type: 'Movie',
            providerIds: {'tmdb': '99'},
            lastPlayedDate: '2026-06-21T00:00:00Z',
          ),
        ]),
        _server('s3', 'S3', [
          _item(
            'c',
            'M',
            type: 'Movie',
            providerIds: {'TMDB': '99'},
            lastPlayedDate: '2026-06-22T00:00:00Z',
          ),
        ]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's3');
    });
  });

  group('null LastPlayedDate handling', () {
    test('null loses to valid date', () {
      final results = [
        _server('s1', 'S1', [
          _item('a', 'M', type: 'Movie', providerIds: {'Tmdb': '1'}),
        ]),
        _server('s2', 'S2', [
          _item(
            'b',
            'M',
            type: 'Movie',
            providerIds: {'Tmdb': '1'},
            lastPlayedDate: '2026-06-01T00:00:00Z',
          ),
        ]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's2');
    });

    test('both null keeps first encountered', () {
      final results = [
        _server('s1', 'S1', [
          _item('a', 'M', type: 'Movie', providerIds: {'Tmdb': '1'}),
        ]),
        _server('s2', 'S2', [
          _item('b', 'M', type: 'Movie', providerIds: {'Tmdb': '1'}),
        ]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's1');
    });
  });

  group('transitive matching', () {
    test('A(tmdb:1) B(tmdb:1,imdb:tt2) C(imdb:tt2) collapse to one', () {
      final results = [
        _server('s1', 'S1', [
          _item(
            'a',
            'M',
            type: 'Movie',
            providerIds: {'Tmdb': '1'},
            lastPlayedDate: '2026-06-20T00:00:00Z',
          ),
        ]),
        _server('s2', 'S2', [
          _item(
            'b',
            'M',
            type: 'Movie',
            providerIds: {'Tmdb': '1', 'Imdb': 'tt2'},
            lastPlayedDate: '2026-06-22T00:00:00Z',
          ),
        ]),
        _server('s3', 'S3', [
          _item(
            'c',
            'M',
            type: 'Movie',
            providerIds: {'Imdb': 'tt2'},
            lastPlayedDate: '2026-06-21T00:00:00Z',
          ),
        ]),
      ];
      final entries = flatten(results, deduplicateAcrossServers: true);
      expect(entries.length, 1);
      expect(entries.first.source.serverId, 's2');
    });
  });
}
