import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/cupertino.dart';
import 'package:nut_player/nut_player.dart';
import 'package:nut_player_example/src/common/models/option_data.dart';
import 'package:nut_player_example/src/common/repository/settings_repository.dart';
import 'package:nut_player_example/src/features/player_screen/domain/model/subtitle.dart';
import 'package:nut_player_example/src/features/player_screen/domain/model/video_quality.dart';
import 'package:nut_player_example/src/features/player_screen/mapper/settings_mapper.dart';
import 'package:nut_player_example/src/features/settings_screen/domain/settings_bloc.dart';

part 'playerview_event.dart';
part 'playerview_state.dart';

class PlayerViewBloc extends Bloc<PlayerViewEvent, PlayerViewState> {
  final VideoPlayerController controller;
  late SettingsRepository startSettings;
  late VoidCallback? _subtitlesListener;
  late VoidCallback? _qualitiesListener;
  late VoidCallback? _isPlayingListener;

  PlayerViewBloc({required Provider provider, required SettingsRepository repository}):
      controller = VideoPlayerController.provider(provider)
        ..initialize(params: {'enablePip': repository.isPipAvailable,
                              'enableAutostart': repository.isAutostart,
                              'enableSettings': repository.isSettingsAvailable,
                              'enableFullscreen': repository.fullscreenSettings != RepositoryFullscreenSettings.off,
                              'isSkinByDefault': repository.isSkinByDefault,
                              'startPosition': repository.start,
                              'enableLoop': repository.isLoop,
                              'quality': SettingsMapper.qualityDictionary(repository.quality),
                              'qualityNaming': repository.qualityNaming.name,
                              'timeouts': {
                                  'playlist': repository.playlist,
                                  'track': repository.track,
                                  'chunk': repository.chunk
                              }}),
        super(PlayerViewController.createFromRepository(repository)) {
    startSettings = repository;
    _onInitialize(repository);
    on<DismissEvent>(_onDismissEvent);
    on<PlayEvent>(_onPlayEvent);
    on<PauseEvent>(_onPauseEvent);
    on<EndEvent>(_onEndEvent);
    on<SeekEvent>(_onSeekEvent);
    on<SpeedChangedEvent>(_onSpeedChangedEvent);
    on<VolumeChangedEvent>(_onVolumeChangedEvent);
    on<QualityChangedEvent>(_onQualityChangedEvent);
    on<SubsChangedEvent>(_onSubsChangedEvent);
    on<StartPositionChangedEvent>(_onStartPositionChangedEvent);
    on<SubsReceivedEvent>(_onSubsReceivedEvent);
    on<QualitiesReceivedEvent>(_onQualitiesReceivedEvent);
  }

  _onInitialize(SettingsRepository repository) {
    controller.setLog(repository.log.key);
    controller.setVolume(repository.volume);
    controller.setBrightness(repository.brightness);

    _isPlayingListener = () {
      final isPlaying = controller.value.isPlaying;
      if (isPlaying) {
        final playerState = state;
        if (playerState is! PlayerViewController) { return; }

        controller.setPlaybackSpeed(playerState.speed);
        _listenToChanges(_isPlayingListener, false);
      }
    };

    _listenToChanges(_isPlayingListener, true);

    _qualitiesListener = () {
      final qualities = controller.value.qualities;
      if (qualities != null && qualities.isNotEmpty) {
          add(QualitiesReceivedEvent(qualities));
          _listenToChanges(_qualitiesListener, false);
          _qualitiesListener = null;
      }
    };

    _listenToChanges(_qualitiesListener, true);

    if (!repository.isSubtitlesAvailable) { return; }
    _subtitlesListener = () {
      final subtitles = controller.value.subtitles;
      if (subtitles != null) {
        if (subtitles.length > 1) { add(SubsReceivedEvent(subtitles)); }
        _listenToChanges(_subtitlesListener, false);
        _subtitlesListener = null;
      }
    };

    _listenToChanges(_subtitlesListener, true);
  }
  
  _listenToChanges(VoidCallback? listener, bool listen) {
    if (listener != null) {
      listen ? controller.addListener(listener) : controller.removeListener(listener);
    }
  }

  _onPlayEvent(PlayEvent event, Emitter<PlayerViewState> emit) {
    controller.play();
  }

  _onPauseEvent(PauseEvent event, Emitter<PlayerViewState> emit) {
    controller.pause();
  }

  _onEndEvent(EndEvent event, Emitter<PlayerViewState> emit) {
    controller.end();
  }

  _onSeekEvent(SeekEvent event, Emitter<PlayerViewState> emit) async {
    final seekTime = event.time;
    controller.seek(Duration(seconds: seekTime.toInt()));
  }

  _onDismissEvent(DismissEvent event, Emitter<PlayerViewState> emit) {
    emit(PlayerViewDismiss());
  }

  _onQualitiesReceivedEvent(QualitiesReceivedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }
    final List<VideoQualityOption> mappedQualities = event.qualities.map((e) =>
        VideoQualityOption(
            id: e["id"],
            bandwidth: e["bandwidth"],
            width: e["width"],
            height: e["height"],
            title: e["title"]
        )
    ).toList();
    final settingsQuality = startSettings.quality;
    final presetQuality = mappedQualities.firstWhere((element) =>
      element.quality.identifier == settingsQuality.identifier,
        orElse: () => mappedQualities.first);
    var sortedQualities = mappedQualities..sort((e1, e2) => e1.height.compareTo(e2.height));
    emit(playerState.copy(qualities: sortedQualities, quality: presetQuality));
  }

  _onSubsReceivedEvent(SubsReceivedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }
    
    var subtitles = event.subs.entries.map((element) => Subtitle(id: element.key, title: element.value)).toList();
    final offSubtitle = subtitles.firstWhere((element) => element.title == "Settings.Subtitles.off");
    final newOffSubtitle = Subtitle(id: offSubtitle.id, title: "Выкл.");
    subtitles.removeWhere((element) => element.title == "Settings.Subtitles.off");
    var sortedSubtitles = subtitles..sort((e1, e2) => e1.title.compareTo(e2.title));
    sortedSubtitles.insert(0, newOffSubtitle);

    emit(playerState.copy(subtitles: sortedSubtitles, currentSubtitle: sortedSubtitles.first));
  }

  _onSubsChangedEvent(SubsChangedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }

    final currentSub = playerState.subtitles?.firstWhere((element) => event.option.value == element.id);
    if (currentSub == null) { return; }

    controller.setSubtitles(currentSub.id);

    emit(playerState.copy(currentSubtitle: currentSub));
  }

  _onSpeedChangedEvent(SpeedChangedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }

    final speedWithoutX = event.option.title.replaceAll(RegExp('x'), '');
    final speed = double.tryParse(speedWithoutX) ?? 1;
    controller.setPlaybackSpeed(speed);

    emit(playerState.copy(speed: speed));
  }

  _onVolumeChangedEvent(VolumeChangedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }

    final volume = double.parse(event.option.title);
    controller.setVolume(volume);

    emit(playerState.copy(volume: volume));
  }

  _onStartPositionChangedEvent(StartPositionChangedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }

    final duration = Duration(seconds: event.value);
    controller.seek(duration);

    emit(playerState.copy(start: event.value));
  }

  _onQualityChangedEvent(QualityChangedEvent event, Emitter<PlayerViewState> emit) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return; }

    final currentQuality = playerState.qualities?.firstWhere((element) => event.option.value == element.id);
    if (currentQuality == null) { return; }

    controller.setQuality(currentQuality.id);

    emit(playerState.copy(quality: currentQuality));
  }

  int currentNumericValue(NumericOptionData setting) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return 0; }

    if (setting.key == const Key('PlaybackOptionStartPositionID')) {
      return playerState.start ?? 0;
    } else {
      return 0;
    }
  }

  int selectedIndex(OptionDataContainer setting) {
    if (setting.key == const Key('PlaybackVolumeSettingID')) {
      return _selectedIndexForVolume(setting.options);
    } else if (setting.key == const Key('PlaybackSpeedSettingID')) {
      return _selectedIndexForSpeed(setting.options);
    } else if (setting.key == const Key('PlaybackSubsSettingID')) {
      return _selectedIndexForSubtitle(setting.options);
    } else if (setting.key == const Key('PlaybackQualitySettingID')) {
      return _selectedIndexForQuality(setting.options);
    } else {
      return 0;
    }
  }

  int _selectedIndexForSpeed(List<OptionData> settings) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return 3; }

    return settings.indexWhere((element) => element.value == playerState.speed.toString());
  }

  int _selectedIndexForVolume(List<OptionData> settings) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return 3; }

    return settings.indexWhere((element) => element.value == playerState.volume.toString());
  }

  int _selectedIndexForSubtitle(List<OptionData> settings) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return 0; }

    return settings.indexWhere((element) => element.value == playerState.currentSubtitle?.id);
  }

  int _selectedIndexForQuality(List<OptionData> settings) {
    final playerState = state;
    if (playerState is! PlayerViewController) { return 0; }

    return settings.indexWhere((element) => element.value == playerState.quality?.id);
  }
}