import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/media/media_backend.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/media/media_source_info.dart';
import 'package:plezy/mpv/mpv.dart';
import 'package:plezy/services/playback_initialization_types.dart';
import 'package:plezy/services/playback_subtitle_resolver.dart';

import '../test_helpers/media_items.dart';

MediaSubtitleTrack _sourceSubtitle(
  int id, {
  String language = 'eng',
  String codec = 'srt',
  bool forced = false,
  bool selected = false,
  bool external = false,
  bool usesExternalDelivery = false,
}) {
  return MediaSubtitleTrack(
    id: id,
    language: language,
    languageCode: language,
    codec: codec,
    title: 'Subtitle $id',
    selected: selected,
    forced: forced,
    external: external,
    usesExternalDelivery: usesExternalDelivery,
  );
}

PlaybackSubtitleSidecar _sidecar(
  int id, {
  String language = 'eng',
  bool isDefault = false,
  bool preload = false,
  bool isContainer = false,
  String? uri,
}) {
  final sidecarUri = uri ?? 'https://example.test/subtitles/$id.srt';
  return PlaybackSubtitleSidecar(
    sourceStreamId: id,
    preload: preload,
    track: isContainer
        ? SubtitleTrack(
            id: 'container:$id',
            title: 'Subtitle $id',
            language: language,
            codec: 'srt',
            isDefault: isDefault,
            isExternal: true,
            isContainer: true,
            uri: sidecarUri,
          )
        : SubtitleTrack.uri(sidecarUri, title: 'Subtitle $id', language: language, codec: 'srt', isDefault: isDefault),
  );
}

MediaSourceInfo _mediaInfo(List<MediaSubtitleTrack> subtitles) {
  return MediaSourceInfo(videoUrl: '', audioTracks: const [], subtitleTracks: subtitles, chapters: const []);
}

void main() {
  group('source subtitle routing', () {
    test('matches an embedded source to its loaded native track', () {
      final source = _sourceSubtitle(2, language: 'eng');
      const native = SubtitleTrack(id: '7', language: 'eng', codec: 'srt');

      expect(
        PlaybackSubtitleResolver.nativeTrackForSource(
          sourceTrack: source,
          nativeTracks: const [native],
          allSourceTracks: [source],
          isResolvedSidecar: false,
          isContainerSidecar: false,
        ),
        native,
      );
    });

    test('does not fuzzy-match a different loaded external sidecar', () {
      final source = MediaSubtitleTrack(
        id: 2,
        codec: 'srt',
        languageCode: 'eng',
        key: '/library/streams/2',
        external: true,
        selected: false,
        forced: false,
      );
      const other = SubtitleTrack(
        id: '9',
        language: 'eng',
        codec: 'srt',
        isExternal: true,
        uri: 'https://server/library/streams/9.srt',
      );

      expect(
        PlaybackSubtitleResolver.nativeTrackForSource(
          sourceTrack: source,
          nativeTracks: const [other],
          allSourceTracks: [source],
          isResolvedSidecar: true,
          isContainerSidecar: false,
        ),
        isNull,
      );
    });

    test('cycles through typed off and every authoritative source id, including zero', () {
      final tracks = [_sourceSubtitle(0), _sourceSubtitle(2)];

      expect(
        PlaybackSubtitleResolver.advanceSourceChoice(tracks, const PlaybackSourceSubtitleChoice.off(), 1),
        const PlaybackSourceSubtitleChoice.source(0),
      );
      expect(
        PlaybackSubtitleResolver.advanceSourceChoice(tracks, const PlaybackSourceSubtitleChoice.source(0), 1),
        const PlaybackSourceSubtitleChoice.source(2),
      );
      expect(
        PlaybackSubtitleResolver.advanceSourceChoice(tracks, const PlaybackSourceSubtitleChoice.source(2), 1),
        const PlaybackSourceSubtitleChoice.off(),
      );
      expect(
        PlaybackSubtitleResolver.advanceSourceChoice(tracks, const PlaybackSourceSubtitleChoice.off(), 4),
        const PlaybackSourceSubtitleChoice.source(0),
      );
    });

    test('fuzzy-matches Jellyfin external-delivery rows that remain embedded in direct play', () {
      final source = _sourceSubtitle(2, language: 'eng', usesExternalDelivery: true);
      const native = SubtitleTrack(id: '7', language: 'eng', codec: 'srt');

      expect(
        PlaybackSubtitleResolver.nativeTrackForSource(
          sourceTrack: source,
          nativeTracks: const [native],
          allSourceTracks: [source],
          isResolvedSidecar: false,
          isContainerSidecar: false,
        ),
        native,
      );
    });
    test('matches a requested source among tracks from one container sidecar', () {
      final sources = [_sourceSubtitle(2, language: 'eng'), _sourceSubtitle(3, language: 'eng')];
      const nativeTracks = [
        SubtitleTrack(
          id: '7',
          title: 'Subtitle 2',
          language: 'eng',
          codec: 'srt',
          isExternal: true,
          isContainer: true,
          uri: 'https://example.test/video.mkv',
        ),
        SubtitleTrack(
          id: '8',
          title: 'Subtitle 3',
          language: 'eng',
          codec: 'srt',
          isExternal: true,
          isContainer: true,
          uri: 'https://example.test/video.mkv',
        ),
      ];

      expect(
        PlaybackSubtitleResolver.nativeTrackForSource(
          sourceTrack: sources.last,
          nativeTracks: nativeTracks,
          allSourceTracks: sources,
          isResolvedSidecar: true,
          isContainerSidecar: true,
        ),
        nativeTracks.last,
      );
    });
  });

  final metadata = testMediaItem(id: 'movie-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie);

  test('attaches only the server-selected sidecar from the full catalog', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([
        _sourceSubtitle(2, selected: true, usesExternalDelivery: true),
        _sourceSubtitle(3, language: 'swe', usesExternalDelivery: true),
      ]),
      sidecars: [
        _sidecar(2),
        _sidecar(3, language: 'swe'),
      ],
    );

    expect(result.primarySourceStreamId, 2);
    expect(result.sidecarsAtOpen, hasLength(1));
    expect(result.sidecarsAtOpen.single.uri, 'https://example.test/subtitles/2.srt');
  });

  test('explicit off produces an open with zero sidecars', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([_sourceSubtitle(2, selected: true, usesExternalDelivery: true)]),
      sidecars: [_sidecar(2)],
      preferredSubtitleTrack: SubtitleTrack.off,
    );

    expect(result.isOff, isTrue);
    expect(result.sidecarsAtOpen, isEmpty);
  });

  test('retains each source metadata row for a shared preloaded container', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([_sourceSubtitle(2, selected: true), _sourceSubtitle(3)]),
      sidecars: [
        _sidecar(2, preload: true, isContainer: true, uri: 'https://example.test/video.mkv'),
        _sidecar(3, preload: true, isContainer: true, uri: 'https://example.test/video.mkv'),
      ],
      preferredSubtitleTrack: SubtitleTrack.off,
    );

    expect(result.isOff, isTrue);
    expect(result.sidecarsAtOpen, hasLength(2));
    expect(result.sidecarsAtOpen.every((track) => track.isContainer), isTrue);
    expect(result.sidecarsAtOpen.map((track) => track.id), ['container:2', 'container:3']);
    expect(result.sidecarsAtOpen.map((track) => track.uri).toSet(), {'https://example.test/video.mkv'});
  });

  test('explicit source selection wins over the server default', () {
    final mediaInfo = _mediaInfo([
      _sourceSubtitle(2, selected: true, usesExternalDelivery: true),
      _sourceSubtitle(3, language: 'swe', usesExternalDelivery: true),
    ]);
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: mediaInfo,
      sidecars: [
        _sidecar(2),
        _sidecar(3, language: 'swe'),
      ],
      preferredSubtitleTrack: PlaybackSubtitleResolver.preferredTrackForSource(mediaInfo, 3),
    );

    expect(result.primarySourceStreamId, 3);
    expect(result.sidecarsAtOpen.single.uri, 'https://example.test/subtitles/3.srt');
  });

  test('explicit source identity wins when subtitle metadata is identical', () {
    final mediaInfo = _mediaInfo([
      MediaSubtitleTrack(
        id: 2,
        language: 'eng',
        languageCode: 'eng',
        codec: 'ass',
        title: 'English',
        selected: true,
        forced: false,
      ),
      MediaSubtitleTrack(
        id: 3,
        language: 'eng',
        languageCode: 'eng',
        codec: 'ass',
        title: 'English',
        selected: false,
        forced: false,
      ),
    ]);
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: mediaInfo,
      sidecars: [
        _sidecar(2, isContainer: true, uri: 'https://example.test/video.mkv'),
        _sidecar(3, isContainer: true, uri: 'https://example.test/video.mkv'),
      ],
      preferredSubtitleTrack: PlaybackSubtitleResolver.preferredTrackForSource(mediaInfo, 3),
    );

    expect(result.primarySourceStreamId, 3);
  });

  test('source identity is ignored across media-source changes', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([
        _sourceSubtitle(3, language: 'eng', selected: true, usesExternalDelivery: true),
        _sourceSubtitle(7, language: 'fra', usesExternalDelivery: true),
      ]),
      sidecars: [
        _sidecar(3),
        _sidecar(7, language: 'fra'),
      ],
      preferredSubtitleTrack: const SubtitleTrack(
        id: 'source:3',
        title: 'French from the previous source',
        language: 'fra',
        codec: 'srt',
        isExternal: true,
        uri: 'https://example.test/previous/3.srt',
      ),
      preserveSourceIdentity: false,
    );

    expect(result.primarySourceStreamId, 7);
  });

  test('unmatched source identity cannot bind a reused id after a source change', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([_sourceSubtitle(3, language: 'eng'), _sourceSubtitle(7, language: 'spa', selected: true)]),
      sidecars: const [],
      preferredSubtitleTrack: const SubtitleTrack(
        id: 'source:3',
        title: 'French from the previous source',
        language: 'fra',
        codec: 'srt',
      ),
      preserveSourceIdentity: false,
    );

    expect(result.primarySourceStreamId, 7);
  });

  test('item-change semantic preference selects the matching new sidecar', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([
        _sourceSubtitle(7, language: 'eng', usesExternalDelivery: true),
        _sourceSubtitle(9, language: 'fra', usesExternalDelivery: true),
      ]),
      sidecars: [
        _sidecar(7),
        _sidecar(9, language: 'fra'),
      ],
      preferredSubtitleTrack: const SubtitleTrack(
        id: 'navigation',
        title: 'English from the previous episode',
        language: 'eng',
        codec: 'srt',
        isExternal: true,
      ),
    );

    expect(result.primarySourceStreamId, 7);
    expect(result.primarySidecar?.track.uri, 'https://example.test/subtitles/7.srt');
    expect(result.sidecarsAtOpen, hasLength(1));
  });

  test('item-change semantic preference distinguishes forced and full subtitles in one language', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([
        _sourceSubtitle(7, language: 'eng', usesExternalDelivery: true),
        _sourceSubtitle(8, language: 'eng', forced: true, usesExternalDelivery: true),
      ]),
      sidecars: [_sidecar(7), _sidecar(8)],
      preferredSubtitleTrack: const SubtitleTrack(
        id: 'navigation',
        title: 'English forced from the previous episode',
        language: 'eng',
        codec: 'srt',
        isForced: true,
        isExternal: true,
      ),
    );

    expect(result.primarySourceStreamId, 8);
    expect(result.primaryTrack.isForced, isTrue);
    expect(result.primarySidecar?.track.uri, 'https://example.test/subtitles/8.srt');
  });

  test('selected embedded subtitle keeps sidecars out of the open', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([_sourceSubtitle(2, selected: true)]),
      sidecars: const [],
    );

    expect(result.isOff, isFalse);
    expect(result.primarySourceStreamId, 2);
    expect(result.primarySidecar, isNull);
    expect(result.sidecarsAtOpen, isEmpty);
  });

  test('selected metadata-free embedded subtitle resolves by source identity', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: _mediaInfo([MediaSubtitleTrack(id: 2, codec: 'ass', selected: true, forced: false)]),
      sidecars: const [],
    );

    expect(result.isOff, isFalse);
    expect(result.primarySourceStreamId, 2);
  });

  test('preferred secondary subtitle attaches a second distinct sidecar', () {
    final mediaInfo = _mediaInfo([
      _sourceSubtitle(2, selected: true, usesExternalDelivery: true),
      _sourceSubtitle(3, language: 'swe', usesExternalDelivery: true),
    ]);
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: mediaInfo,
      sidecars: [
        _sidecar(2),
        _sidecar(3, language: 'swe'),
      ],
      preferredSecondarySubtitleTrack: PlaybackSubtitleResolver.preferredTrackForSource(mediaInfo, 3),
    );

    expect(result.primarySourceStreamId, 2);
    expect(result.secondarySourceStreamId, 3);
    expect(result.sidecarsAtOpen, hasLength(2));
  });

  test('legacy sidecar without source metadata still follows default selection', () {
    final result = PlaybackSubtitleResolver.resolve(
      metadata: metadata,
      mediaInfo: null,
      sidecars: [
        PlaybackSubtitleSidecar(
          sourceStreamId: null,
          track: SubtitleTrack.uri('file:///tmp/subtitle.srt', title: 'Local', isDefault: true),
        ),
      ],
    );

    expect(result.isOff, isFalse);
    expect(result.primarySourceStreamId, isNull);
    expect(result.sidecarsAtOpen.single.uri, 'file:///tmp/subtitle.srt');
  });
}
