import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';

import '../../cache/offline_provider.dart';
import '../../library/widgets/play_circle_button.dart';
import '../../models/playlist.dart';
import '../../models/track.dart';
import '../../player/player_provider.dart';
import '../../shared/widgets/server_image.dart';
import '../../theme/theme_extension.dart';
import '../playlists_provider.dart';

/// Mirrors the web PlaylistCard. ~176dp wide, square cover (~144dp),
/// name + optional system-variant badge below. Tap pushes
/// `/playlists/{id}`. The bottom-right play button fetches the
/// playlist and starts playback from track 0; disabled when the
/// playlist has no tracks.
class PlaylistCard extends ConsumerWidget {
  const PlaylistCard({super.key, required this.playlist});

  final Playlist playlist;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final fs = Theme.of(context).extension<FabledSwordTheme>()!;
    // #427 S4: refreshable system playlists need the live build/
    // shuffle endpoints — unavailable offline. Disable their play
    // (use Shuffle all instead). User playlists play from cache.
    final offline = ref.watch(offlineProvider);
    final hasTracks = playlist.trackCount > 0 &&
        !(offline && playlist.refreshable);
    return SizedBox(
      width: 176,
      child: Material(
        color: Colors.transparent,
        child: InkWell(
          onTap: () =>
              context.push('/playlists/${playlist.id}', extra: playlist),
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8),
            child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
              // Stack: collage + overlaid play button at bottom-right.
              Stack(
                children: [
                  ClipRRect(
                    borderRadius: BorderRadius.circular(6),
                    child: Container(
                      width: 144,
                      height: 144,
                      color: fs.slate,
                      // coverUrl is deterministic per playlist (see
                      // CachedPlaylistAdapter.toRef). When the server's
                      // collage isn't built yet, ServerImage's
                      // errorBuilder shows the queue_music icon over the
                      // slate background.
                      child: playlist.coverUrl.isEmpty
                          ? Icon(LucideIcons.list_music, color: fs.ash, size: 56)
                          : ServerImage(
                              url: playlist.coverUrl,
                              fit: BoxFit.cover,
                              fallback:
                                  Icon(LucideIcons.list_music, color: fs.ash, size: 56),
                            ),
                    ),
                  ),
                  Positioned(
                    bottom: 6,
                    right: 6,
                    child: PlayCircleButton(
                      enabled: hasTracks,
                      onPressed: () => _playPlaylist(context, ref),
                    ),
                  ),
                  // System playlists get a refresh affordance so the
                  // user can force a fresh mix instead of waiting for
                  // the daily 03:00 rebuild. Mirrors the web kebab.
                  if (playlist.refreshable)
                    Positioned(
                      top: 2,
                      right: 2,
                      child: PopupMenuButton<String>(
                        icon: Icon(LucideIcons.ellipsis_vertical, color: fs.parchment, size: 18),
                        tooltip: 'Playlist actions',
                        color: fs.iron,
                        onSelected: (_) => _refresh(context, ref),
                        itemBuilder: (_) => [
                          PopupMenuItem<String>(
                            value: 'refresh',
                            child: Row(children: [
                              Icon(LucideIcons.refresh_cw, color: fs.parchment, size: 18),
                              const SizedBox(width: 8),
                              Text(_refreshLabel,
                                  style: TextStyle(color: fs.parchment)),
                            ]),
                          ),
                        ],
                      ),
                    ),
                ],
              ),
              const SizedBox(height: 8),
              Text(
                playlist.name,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(color: fs.parchment, fontSize: 14),
              ),
              if (playlist.isSystem)
                Padding(
                  padding: const EdgeInsets.only(top: 2),
                  child: Container(
                    padding:
                        const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
                    decoration: BoxDecoration(
                      color: fs.iron,
                      borderRadius: BorderRadius.circular(4),
                    ),
                    child: Text(
                      playlist.systemVariant!.replaceAll('_', ' '),
                      style: TextStyle(color: fs.accent, fontSize: 11),
                    ),
                  ),
                ),
              if (playlist.isSystem && _refreshedLabel(playlist.createdAt) != '')
                Padding(
                  padding: const EdgeInsets.only(top: 2),
                  child: Text(
                    _refreshedLabel(playlist.createdAt),
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(color: fs.ash, fontSize: 11),
                  ),
                ),
            ]),
          ),
        ),
      ),
    );
  }

  String get _refreshLabel => 'Refresh ${playlist.name}';

  /// #417: system playlists atomic-replace on rebuild, so createdAt
  /// is the last-rotated time. Friendly wording mirrors the web
  /// PlaylistCard. Empty string when the timestamp is unparseable.
  String _refreshedLabel(String iso) {
    final t = DateTime.tryParse(iso);
    if (t == null) return '';
    final now = DateTime.now();
    final mins = now.difference(t).inMinutes;
    if (mins < 5) return 'Refreshed just now';
    final startOfToday = DateTime(now.year, now.month, now.day);
    if (!t.isBefore(startOfToday)) return 'Refreshed today';
    final days = startOfToday.difference(DateTime(t.year, t.month, t.day)).inDays;
    if (days <= 1) return 'Refreshed yesterday';
    if (days < 7) return 'Refreshed $days days ago';
    const months = [
      'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
    ];
    return 'Refreshed ${months[t.month - 1]} ${t.day}';
  }

  /// Forces a server-side rebuild of this system playlist, then
  /// invalidates the aggregate list so the rotated UUID + new tracks
  /// land on the next read. ScaffoldMessenger is captured before the
  /// await so we don't touch a stale BuildContext afterward.
  Future<void> _refresh(BuildContext context, WidgetRef ref) async {
    final messenger = ScaffoldMessenger.of(context);
    try {
      final api = await ref.read(playlistsApiProvider.future);
      await api.refreshSystem(playlist.systemVariant!);
      ref.invalidate(playlistsListProvider);
      messenger.showSnackBar(
        SnackBar(content: Text('${playlist.name} refreshed')),
      );
    } catch (e) {
      messenger.showSnackBar(
        SnackBar(content: Text("Couldn't refresh: $e")),
      );
    }
  }

  /// Fetches the playlist via /api/playlists/{id}, materializes each
  /// PlaylistTrack into a TrackRef (filtering out unavailable rows
  /// whose `trackId` is null after a track-delete), and plays from
  /// index 0. Mirrors the web PlaylistCard's onPlayClick.
  ///
  /// Failure modes are surfaced via SnackBar so a not-yet-built mix
  /// or a slow server doesn't look like the tap was ignored.
  Future<void> _playPlaylist(BuildContext context, WidgetRef ref) async {
    final messenger = ScaffoldMessenger.of(context);
    final api = await ref.read(playlistsApiProvider.future);
    final PlaylistDetail detail;
    try {
      // Refreshable (singleton) system playlists: fetch the server's
      // rotation-aware order (#415) and play as-is, tagged with the
      // source so the reporter advances rotation. User playlists AND
      // non-singleton system kinds (songs_like_artist — no by-kind
      // endpoint) play the stored order via get(), untagged.
      detail = await (playlist.refreshable
              ? api.systemShuffle(playlist.systemVariant!)
              : api.get(playlist.id))
          .timeout(const Duration(seconds: 8));
    } on TimeoutException {
      messenger.showSnackBar(const SnackBar(
        content: Text("Couldn't load playlist — check your connection"),
      ));
      return;
    } catch (e) {
      messenger.showSnackBar(SnackBar(
        content: Text('Playlist load failed: $e'),
      ));
      return;
    }
    final refs = <TrackRef>[];
    for (final t in detail.tracks) {
      if (t.trackId == null) continue;
      refs.add(TrackRef(
        id: t.trackId!,
        title: t.title,
        albumId: t.albumId ?? '',
        albumTitle: t.albumTitle,
        artistId: t.artistId ?? '',
        artistName: t.artistName,
        durationSec: t.durationSec,
        streamUrl: t.streamUrl ?? '',
      ));
    }
    if (refs.isEmpty) {
      messenger.showSnackBar(const SnackBar(
        content: Text("Mix isn't ready yet — try again in a moment"),
      ));
      return;
    }
    // Rotation-aware kinds arrive pre-ordered from the server — play
    // as-is, tagged so the reporter advances rotation. No client
    // shuffle. Everything else plays stored order, untagged.
    await ref.read(playerActionsProvider).playTracks(
          refs,
          initialIndex: 0,
          source: playlist.refreshable ? playlist.systemVariant : null,
        );
  }
}
