import 'dart:async';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:flutter_broadcasts/flutter_broadcasts.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:audio_session/audio_session.dart';
import 'package:rhythm_box/platform.dart';
import 'package:rhythm_box/providers/error_notifier.dart';

// ignore: implementation_imports
import 'package:rhythm_box/services/audio_player/playback_state.dart';

/// MediaKit [Player] by default doesn't have a state stream.
/// This class adds a state stream to the [Player] class.
class CustomPlayer extends Player {
  final StreamController<AudioPlaybackState> _playerStateStream;
  final StreamController<bool> _shuffleStream;

  late final List<StreamSubscription> _subscriptions;

  bool _shuffled;
  int _androidAudioSessionId = 0;
  String _packageName = '';
  AndroidAudioManager? _androidAudioManager;

  CustomPlayer({super.configuration})
      : _playerStateStream = StreamController.broadcast(),
        _shuffleStream = StreamController.broadcast(),
        _shuffled = false {
    nativePlayer.setProperty('network-timeout', '120');

    _subscriptions = [
      stream.buffering.listen((event) {
        _playerStateStream.add(AudioPlaybackState.buffering);
      }),
      stream.playing.listen((playing) {
        if (playing) {
          _playerStateStream.add(AudioPlaybackState.playing);
        } else {
          _playerStateStream.add(AudioPlaybackState.paused);
        }
      }),
      stream.completed.listen((isCompleted) async {
        if (!isCompleted) return;
        _playerStateStream.add(AudioPlaybackState.completed);
      }),
      stream.playlist.listen((event) {
        if (event.medias.isEmpty) {
          _playerStateStream.add(AudioPlaybackState.stopped);
        }
      }),
      stream.error.listen((event) {
        Get.find<ErrorNotifier>().logError('[Playback] Error: $event');
      }),
    ];
    PackageInfo.fromPlatform().then((packageInfo) {
      _packageName = packageInfo.packageName;
    });
    if (PlatformInfo.isAndroid) {
      _androidAudioManager = AndroidAudioManager();
      AudioSession.instance.then((s) async {
        _androidAudioSessionId =
            await _androidAudioManager!.generateAudioSessionId();
        notifyAudioSessionUpdate(true);

        await nativePlayer.setProperty(
          'audiotrack-session-id',
          _androidAudioSessionId.toString(),
        );
        await nativePlayer.setProperty('ao', 'audiotrack,opensles,');
      });
    }
  }

  Future<void> notifyAudioSessionUpdate(bool active) async {
    if (PlatformInfo.isAndroid) {
      sendBroadcast(
        BroadcastMessage(
          name: active
              ? 'android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION'
              : 'android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION',
          data: {
            'android.media.extra.AUDIO_SESSION': _androidAudioSessionId,
            'android.media.extra.PACKAGE_NAME': _packageName
          },
        ),
      );
    }
  }

  bool get shuffled => _shuffled;

  Stream<AudioPlaybackState> get playerStateStream => _playerStateStream.stream;
  Stream<bool> get shuffleStream => _shuffleStream.stream;
  Stream<int> get indexChangeStream {
    int oldIndex = state.playlist.index;
    return stream.playlist.map((event) => event.index).where((newIndex) {
      if (newIndex != oldIndex) {
        oldIndex = newIndex;
        return true;
      }
      return false;
    });
  }

  @override
  Future<void> setShuffle(bool shuffle) async {
    _shuffled = shuffle;
    await super.setShuffle(shuffle);
    _shuffleStream.add(shuffle);
    await Future.delayed(const Duration(milliseconds: 100));
    if (shuffle) {
      await move(state.playlist.index, 0);
    }
  }

  @override
  Future<void> stop() async {
    await super.stop();

    _shuffled = false;
    _playerStateStream.add(AudioPlaybackState.stopped);
    _shuffleStream.add(false);
  }

  @override
  Future<void> dispose() async {
    for (var element in _subscriptions) {
      element.cancel();
    }
    await notifyAudioSessionUpdate(false);
    return super.dispose();
  }

  NativePlayer get nativePlayer => platform as NativePlayer;

  Future<void> insert(int index, Media media) async {
    await add(media);
    await move(state.playlist.medias.length, index);
  }

  Future<void> setAudioNormalization(bool normalize) async {
    if (normalize) {
      await nativePlayer.setProperty('af', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5');
    } else {
      await nativePlayer.setProperty('af', '');
    }
  }
}
