import 'package:flutter_test/flutter_test.dart';
import 'package:smplayer/core/contracts/library.dart';
import 'package:smplayer/core/contracts/playback.dart';
import 'package:smplayer/core/domain/ports/emby_gateway.dart';
import 'package:smplayer/core/infra/emby_api/playback_resolver.dart';


class _UnusedEmbyGateway implements EmbyGateway {
  @override
  dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}

void main() {
  const ctx = AuthedRequestContext(
    baseUrl: 'https://emby.example.com',
    token: 'tok123',
    userId: 'user1',
  );

  final resolver = PlaybackResolver(
    gateway: _UnusedEmbyGateway(),
    deviceId: 'device-1',
    deviceName: 'TestBox',
  );

  EmbyRawItem item({int? resumeTicks}) => EmbyRawItem(
    Id: 'item1',
    Name: 'Movie One',
    Type: 'Movie',
    RunTimeTicks: 600 * 10000000,
    UserData: resumeTicks == null
        ? null
        : EmbyRawUserData(PlaybackPositionTicks: resumeTicks),
  );

  Map<String, dynamic> playbackInfo(Map<String, dynamic> source) => {
    'PlaySessionId': 'ps1',
    'MediaSources': [source],
  };

  group('PlaybackResolver.prepare 流地址优先级', () {
    test('DirectStreamUrl 优先，playMethod=DirectStream', () async {
      final result = await resolver.prepare(
        ctx: ctx,
        payload: const PlaybackRequestPayload(itemId: 'item1'),
        preloadedItem: item(),
        preloadedPlaybackInfo: playbackInfo({
          'Id': 's1',
          'Container': 'mkv',
          'DirectStreamUrl': '/Videos/item1/stream.mkv?x=1',
          'TranscodingUrl': '/Videos/item1/main.m3u8',
          'SupportsDirectStream': true,
        }),
      );
      expect(
        result.streamUrl,
        'https://emby.example.com/Videos/item1/stream.mkv?x=1&api_key=tok123',
      );
      expect(result.playMethod, 'DirectStream');
      expect(result.directStream, isTrue);
    });

    test('无 DirectStreamUrl 时用 TranscodingUrl，playMethod=Transcode', () async {
      final result = await resolver.prepare(
        ctx: ctx,
        payload: const PlaybackRequestPayload(itemId: 'item1'),
        preloadedItem: item(),
        preloadedPlaybackInfo: playbackInfo({
          'Id': 's1',
          'Container': 'mkv',
          'TranscodingUrl': '/Videos/item1/main.m3u8',
        }),
      );
      expect(result.streamUrl, contains('/Videos/item1/main.m3u8'));
      expect(result.streamUrl, contains('api_key=tok123'));
      expect(result.playMethod, 'Transcode');
    });

    test('跨域绝对 CDN 地址不追加 Emby token', () async {
      final result = await resolver.prepare(
        ctx: ctx,
        payload: const PlaybackRequestPayload(itemId: 'item1'),
        preloadedItem: item(),
        preloadedPlaybackInfo: playbackInfo({
          'Id': 's1',
          'Container': 'mkv',
          'DirectStreamUrl':
              'https://cdn.example.net/media/movie.mkv?signature=signed',
        }),
      );

      expect(
        result.streamUrl,
        'https://cdn.example.net/media/movie.mkv?signature=signed',
      );
      expect(result.streamUrl, isNot(contains('tok123')));
      expect(result.streamUrl, isNot(contains('api_key')));
    });

    test('部署在子路径下时保留 Emby base path', () async {
      const subpathContext = AuthedRequestContext(
        baseUrl: 'https://emby.example.com/emby',
        token: 'tok123',
        userId: 'user1',
      );
      final result = await resolver.prepare(
        ctx: subpathContext,
        payload: const PlaybackRequestPayload(itemId: 'item1'),
        preloadedItem: item(),
        preloadedPlaybackInfo: playbackInfo({
          'Id': 's1',
          'Container': 'mkv',
          'DirectStreamUrl': '/Videos/item1/stream.mkv',
        }),
      );

      expect(
        result.streamUrl,
        'https://emby.example.com/emby/Videos/item1/stream.mkv?api_key=tok123',
      );
    });

    test('两者皆无时回退静态流（含容器扩展名与鉴权参数）', () async {
      final result = await resolver.prepare(
        ctx: ctx,
        payload: const PlaybackRequestPayload(itemId: 'item1'),
        preloadedItem: item(),
        preloadedPlaybackInfo: playbackInfo({'Id': 's1', 'Container': 'mp4'}),
      );
      expect(
        result.streamUrl,
        startsWith('https://emby.example.com/Videos/item1/stream.mp4?'),
      );
      expect(result.streamUrl, contains('api_key=tok123'));
      expect(result.streamUrl, contains('MediaSourceId=s1'));
      expect(result.streamUrl, contains('PlaySessionId=ps1'));
      expect(result.playMethod, 'DirectStream');
    });

    test('按 mediaSourceId 选源，未命中回退首源', () async {
      final info = {
        'MediaSources': [
          {'Id': 's1', 'DirectStreamUrl': '/Videos/item1/a.mkv'},
          {'Id': 's2', 'DirectStreamUrl': '/Videos/item1/b.mkv'},
        ],
      };
      final picked = await resolver.prepare(
        ctx: ctx,
        payload: const PlaybackRequestPayload(
          itemId: 'item1',
          mediaSourceId: 's2',
        ),
        preloadedItem: item(),
        preloadedPlaybackInfo: info,
      );
      expect(picked.streamUrl, contains('/Videos/item1/b.mkv'));

      final fallback = await resolver.prepare(
        ctx: ctx,
        payload: const PlaybackRequestPayload(
          itemId: 'item1',
          mediaSourceId: 'missing',
        ),
        preloadedItem: item(),
        preloadedPlaybackInfo: info,
      );
      expect(fallback.streamUrl, contains('/Videos/item1/a.mkv'));
    });
  });

  group('PlaybackResolver.prepare 续播位置', () {
    Future<double> startSecondsFor(PlaybackRequestPayload payload) async {
      final result = await resolver.prepare(
        ctx: ctx,
        payload: payload,
        preloadedItem: item(resumeTicks: 120 * 10000000),
        preloadedPlaybackInfo: playbackInfo({
          'Id': 's1',
          'DirectStreamUrl': '/Videos/item1/a.mkv',
        }),
      );
      return result.startPositionSeconds;
    }

    test('playFromStart 强制从头', () async {
      expect(
        await startSecondsFor(
          const PlaybackRequestPayload(itemId: 'item1', playFromStart: true),
        ),
        0,
      );
    });

    test('显式 startPositionTicks 优先于 UserData', () async {
      expect(
        await startSecondsFor(
          const PlaybackRequestPayload(
            itemId: 'item1',
            startPositionTicks: 60 * 10000000,
          ),
        ),
        60,
      );
    });

    test('默认取 UserData.PlaybackPositionTicks', () async {
      expect(
        await startSecondsFor(const PlaybackRequestPayload(itemId: 'item1')),
        120,
      );
    });
  });

  group('PlaybackResolver.resolveSubtitles', () {
    test('未提供 baseUrl/token 时外挂轨全部过滤，仅留内封（详尽用例见 '
        'playback_resolver_subtitles_test.dart）', () {
      final subtitles = PlaybackResolver.resolveSubtitles(const [
        EmbyRawMediaStream(Index: 1, Type: 'Subtitle', Title: 'embedded'),
        EmbyRawMediaStream(
          Index: 2,
          Type: 'Subtitle',
          Codec: 'srt',
          IsExternal: true,
        ),
        EmbyRawMediaStream(
          Index: 3,
          Type: 'Subtitle',
          Codec: 'srt',
          DeliveryMethod: 'External',
        ),
        EmbyRawMediaStream(
          Index: 4,
          Type: 'Subtitle',
          Codec: 'srt',
          DeliveryUrl: '/subs/4.srt',
        ),
        EmbyRawMediaStream(Index: 5, Type: 'Audio'),
      ]);
      expect(subtitles, hasLength(1));
      expect(subtitles.single.index, 1);
      expect(subtitles.single.title, 'embedded');
    });
  });
}
