import 'dart:math' show Random;

import 'package:audio_service/audio_service.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../api/endpoints/likes.dart' show LikeKind;
import '../api/endpoints/radio.dart';
import '../auth/auth_provider.dart';
import '../cache/audio_cache_manager.dart';
import '../library/library_providers.dart' show dioProvider;
import '../likes/likes_provider.dart';
import '../models/track.dart';
import 'album_cover_cache.dart';
import 'audio_handler.dart';

final audioHandlerProvider = Provider<MinstrelAudioHandler>((ref) {
  throw UnimplementedError('overridden in main()');
});

final albumCoverCacheProvider = Provider<AlbumCoverCache>((ref) {
  return AlbumCoverCache(
    dioFactory: () => ref.read(dioProvider.future),
  );
});

final playbackStateProvider = StreamProvider<PlaybackState>(
  (ref) => ref.watch(audioHandlerProvider).playbackState,
);

final mediaItemProvider = StreamProvider<MediaItem?>(
  (ref) => ref.watch(audioHandlerProvider).mediaItem,
);

final queueProvider = StreamProvider<List<MediaItem>>(
  (ref) => ref.watch(audioHandlerProvider).queue,
);

/// Player volume in [0.0, 1.0]. Mirrors just_audio's player.volume;
/// modify via PlayerActions.setVolume(). Surfaced separately from
/// PlaybackState because audio_service's PlaybackState doesn't carry
/// volume — it's a player-side concern.
final volumeProvider = StreamProvider<double>(
  (ref) => ref.watch(audioHandlerProvider).volumeStream,
);

/// Live playback position. just_audio emits at ~200ms cadence so seek
/// bars driven by this provider scrub smoothly. Don't use
/// PlaybackState.updatePosition for that — it only changes on state
/// transitions (play/pause/buffer/seek) and the bar would appear
/// frozen between events.
final positionProvider = StreamProvider<Duration>(
  (ref) => ref.watch(audioHandlerProvider).positionStream,
);

class PlayerActions {
  PlayerActions(this._ref) {
    // Keep MediaItem.rating in sync with the drift-backed likedIds
    // cache so external media surfaces (Wear's heart button, lock-
    // screen favorite, Auto's like icon) reflect the current state
    // even when the user toggles a like from somewhere other than
    // the watch itself — e.g. tapping the heart on a TrackRow, the
    // kebab menu, or another logged-in device propagating via SSE.
    _ref.listen<AsyncValue<LikedIds>>(likedIdsProvider, (_, next) {
      if (next.value == null) return;
      _ref.read(audioHandlerProvider)
        ..refreshCurrentRating()
        ..refreshFavoriteControl();
    });
  }
  final Ref _ref;

  Future<void> playTracks(
    List<TrackRef> tracks, {
    int initialIndex = 0,
    bool shuffle = false,
    String? source,
  }) async {
    // shuffle=true means "play this pool randomly, starting at a
    // random track" (client-side; used for non-system surfaces that
    // want shuffle). System playlists instead fetch a server-ordered
    // list and pass source — no client shuffle, the order is already
    // rotation-aware (#415).
    var startAt = initialIndex;
    if (shuffle && tracks.length > 1) {
      startAt = Random().nextInt(tracks.length);
    }
    final url   = await _ref.read(serverUrlProvider.future);
    final token = await _ref.read(secureStorageProvider).read(key: 'session_token');
    final cache = _ref.read(albumCoverCacheProvider);
    final audioCache = _ref.read(audioCacheManagerProvider);
    final h = _ref.read(audioHandlerProvider)
      ..configure(
        baseUrl: url ?? '',
        token: token,
        coverCache: cache,
        audioCacheManager: audioCache,
        likeBridge: _buildLikeBridge(),
      );
    await h.setQueueFromTracks(tracks, initialIndex: startAt, source: source);
    if (shuffle) {
      await h.setShuffleMode(AudioServiceShuffleMode.all);
    }
    await h.play();
  }

  /// Rebuilds a previously-persisted queue WITHOUT auto-playing, then
  /// seeks to [position]. The resume-on-launch path (#54): the user
  /// sees their last track in the mini bar, paused, and continues with
  /// play / a media button. Mirrors playTracks' configure step so
  /// streaming works after restore; intentionally no h.play().
  Future<void> restoreQueue(
    List<TrackRef> tracks, {
    int initialIndex = 0,
    Duration position = Duration.zero,
    String? source,
  }) async {
    if (tracks.isEmpty) return;
    final url   = await _ref.read(serverUrlProvider.future);
    final token = await _ref.read(secureStorageProvider).read(key: 'session_token');
    final cache = _ref.read(albumCoverCacheProvider);
    final audioCache = _ref.read(audioCacheManagerProvider);
    final h = _ref.read(audioHandlerProvider)
      ..configure(
        baseUrl: url ?? '',
        token: token,
        coverCache: cache,
        audioCacheManager: audioCache,
        likeBridge: _buildLikeBridge(),
      );
    await h.setQueueFromTracks(tracks, initialIndex: initialIndex, source: source);
    if (position > Duration.zero) {
      await h.seek(position);
    }
  }

  /// Builds the adapter the audio handler uses to read + flip the
  /// current track's like state in response to external media-
  /// controller events (Wear's heart button, lock-screen favorite).
  /// Constructed here because the audio handler is initialized in
  /// main.dart before the ProviderScope exists; passing the bridge
  /// in via configure() keeps the handler Riverpod-agnostic while
  /// still letting it call into LikesController + likedIdsProvider.
  LikeBridge _buildLikeBridge() {
    return LikeBridge(
      toggleTrackLike: (id) =>
          _ref.read(likesControllerProvider).toggle(LikeKind.track, id),
      isTrackLiked: (id) =>
          _ref.read(likedIdsProvider).value?.has(LikeKind.track, id) ?? false,
    );
  }

  Future<void> playNext(TrackRef track) async {
    final h = _ref.read(audioHandlerProvider);
    await h.playNext(track);
  }

  Future<void> enqueue(TrackRef track) async {
    final h = _ref.read(audioHandlerProvider);
    await h.enqueue(track);
  }

  /// Toggles shuffle on/off. Pre-existing audio_service convention.
  Future<void> toggleShuffle() async {
    final h = _ref.read(audioHandlerProvider);
    final state = await h.playbackState.first;
    final next = state.shuffleMode == AudioServiceShuffleMode.none
        ? AudioServiceShuffleMode.all
        : AudioServiceShuffleMode.none;
    await h.setShuffleMode(next);
  }

  /// Cycles repeat mode: none → all → one → none.
  Future<void> cycleRepeat() async {
    final h = _ref.read(audioHandlerProvider);
    final state = await h.playbackState.first;
    final next = switch (state.repeatMode) {
      AudioServiceRepeatMode.none => AudioServiceRepeatMode.all,
      AudioServiceRepeatMode.all => AudioServiceRepeatMode.one,
      _ => AudioServiceRepeatMode.none,
    };
    await h.setRepeatMode(next);
  }

  Future<void> setVolume(double v) async {
    await _ref.read(audioHandlerProvider).setVolume(v);
  }

  /// Fetches `/api/radio?seed_track=<id>` and starts playing the
  /// returned track list (seed at index 0 + recommended picks).
  Future<void> startRadio(String trackId) async {
    final dio = await _ref.read(dioProvider.future);
    final tracks = await RadioApi(dio).seedTrack(trackId);
    if (tracks.isEmpty) return;
    await playTracks(tracks);
  }
}

final playerActionsProvider = Provider<PlayerActions>((ref) => PlayerActions(ref));
