import 'dart:async';

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

import '../../core/contracts/library.dart';
import '../../providers/di_providers.dart';
import '../../providers/session_provider.dart';
import '../../providers/tmdb_settings_provider.dart';
import '../../shared/mappers/tmdb_image_url.dart';
import '../../shared/utils/format_utils.dart';
import '../../shared/utils/media_nav.dart';
import '../../shared/mappers/media_image_url.dart';
import '../../shared/utils/time_bucket.dart';
import '../../shared/widgets/app_loading_ring.dart';
import '../../shared/utils/tmdb_key_utils.dart';
import '../../shared/widgets/app_sliver_header.dart';
import '../../shared/widgets/empty_state.dart';
import '../aggregated/aggregated_grouping.dart';
import '../aggregated/widgets/aggregated_grouped_grid.dart';
import '../aggregated/widgets/aggregated_media_card.dart';
import '../aggregated/widgets/server_filter_bar.dart';
import 'widgets/history_source_picker.dart';

class HistoryPage extends ConsumerStatefulWidget {

  final bool embedded;

  const HistoryPage({super.key, this.embedded = false});

  @override
  ConsumerState<HistoryPage> createState() => _HistoryPageState();
}

class _HistoryPageState extends ConsumerState<HistoryPage> {
  StreamSubscription<GlobalSearchServerResult>? _subscription;

  List<GlobalSearchServerResult> _serverResults = [];
  bool _loading = false;
  bool _switching = false;

  final Set<String> _collapsed = {};
  String? _serverFilter;

  @override
  void initState() {
    super.initState();
    _load();
  }

  @override
  void dispose() {
    _subscription?.cancel();
    super.dispose();
  }

  Future<void> _load() async {
    _subscription?.cancel();
    _subscription = null;

    setState(() {
      _loading = true;
      _serverResults = [];
    });

    try {
      final uc = await ref.read(aggregatedResumeUseCaseProvider.future);
      final stream = uc.executeStream();

      _subscription = stream.listen(
        (result) {
          if (mounted) {
            setState(() => _serverResults = [..._serverResults, result]);
          }
        },
        onDone: () {
          if (mounted) setState(() => _loading = false);
        },
        onError: (_) {
          if (mounted) setState(() => _loading = false);
        },
      );
    } catch (_) {
      if (mounted) setState(() => _loading = false);
    }
  }

  Future<void> _onItemTap(
    BuildContext context,
    GlobalSearchServerResult serverResult,
    EmbyRawItem item,
  ) async {
    if (_switching) return;
    setState(() => _switching = true);
    try {
      await goMediaDetailOnServer(
        context,
        ref,
        serverId: serverResult.serverId,
        item: item,
      );
    } finally {
      if (mounted) setState(() => _switching = false);
    }
  }

  Future<void> _onServerSourcesTap(
    BuildContext context,
    AggregatedEntry entry,
    DateTime now,
  ) async {
    if (_switching || entry.sources.length < 2) return;
    final selectedSource = await showHistorySourcePicker(
      context,
      entry: entry,
      now: now,
    );
    if (selectedSource == null || !mounted || !context.mounted) return;
    await _onItemTap(context, selectedSource.source, selectedSource.item);
  }

  void _toggle(String key) {
    setState(() {
      if (!_collapsed.remove(key)) _collapsed.add(key);
    });
  }

  @override
  Widget build(BuildContext context) {
    ref.listen(sessionProvider, (previous, next) {
      final prevIds =
          previous?.value?.sessions.map((s) => s.serverId).toSet() ??
          <String>{};
      final nextIds =
          next.value?.sessions.map((s) => s.serverId).toSet() ?? <String>{};
      if (prevIds.length == nextIds.length && prevIds.containsAll(nextIds)) {
        return;
      }
      if (_serverFilter != null && !nextIds.contains(_serverFilter)) {
        setState(() => _serverFilter = null);
      }
      _load();
    });

    if (widget.embedded) {
      return Stack(
        fit: StackFit.expand,
        children: [
          CustomScrollView(
            slivers: [
              SliverOverlapInjector(
                handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                  context,
                ),
              ),
              ..._buildContentSlivers(context),
            ],
          ),
          if (_switching) _switchingOverlay(),
        ],
      );
    }

    return Stack(
      fit: StackFit.expand,
      children: [
        Scaffold(
          extendBodyBehindAppBar: true,
          body: CustomScrollView(
            slivers: [
              AppSliverHeader(
                title: '播放记录',
                actions: [
                  IconButton(
                    onPressed: _loading ? null : _load,
                    icon: _loading
                        ? const AppLoadingRing(size: 16)
                        : const Icon(Icons.refresh, size: 18),
                    tooltip: '刷新',
                  ),
                ],
              ),
              ..._buildContentSlivers(context),
            ],
          ),
        ),
        if (_switching) _switchingOverlay(),
      ],
    );
  }

  Widget _switchingOverlay() => const Positioned.fill(
    child: ColoredBox(
      color: Color(0x44000000),
      child: Center(child: AppLoadingRing()),
    ),
  );

  List<Widget> _buildContentSlivers(BuildContext context) {
    if (_serverResults.isEmpty && !_loading) {
      return const [
        SliverFillRemaining(
          hasScrollBody: false,
          child: EmptyState(icon: Icons.play_circle_outline, title: '暂无播放记录'),
        ),
      ];
    }
    if (_serverResults.isEmpty && _loading) {
      return const [
        SliverFillRemaining(
          hasScrollBody: false,
          child: Center(child: AppLoadingRing()),
        ),
      ];
    }

    final now = DateTime.now();
    final entries = flatten(
      _serverResults,
      serverFilter: _serverFilter,
      deduplicateAcrossServers: true,
    );
    final groups = groupEntries(entries, GroupAxis.watchedTime, now);
    final servers = _serverResults
        .map((sr) => (id: sr.serverId, name: sr.serverName))
        .toList(growable: false);

    return [
      SliverToBoxAdapter(
        child: Padding(
          padding: const EdgeInsets.only(top: 8),
          child: ServerFilterBar(
            servers: servers,
            selected: _serverFilter,
            onSelect: (v) => setState(() => _serverFilter = v),
          ),
        ),
      ),
      ...buildAggregatedGridSlivers(
        groups: groups,
        collapsed: _collapsed,
        onToggle: _toggle,
        cardBuilder: (entry) => _card(context, entry, now),
      ),
    ];
  }

  Widget _card(BuildContext context, AggregatedEntry entry, DateTime now) {
    return _TmdbAwareHistoryCard(
      entry: entry,
      now: now,
      imageHeaders: buildEmbyImageHeaders(
        ref,
        token: entry.source.token,
        userId: entry.source.userId,
      ),
      onTap: () => _onItemTap(context, entry.source, entry.item),
      onServerSourcesTap: entry.sources.length > 1
          ? () => unawaited(_onServerSourcesTap(context, entry, now))
          : null,
    );
  }
}

class _TmdbAwareHistoryCard extends ConsumerWidget {
  final AggregatedEntry entry;
  final DateTime now;
  final Map<String, String> imageHeaders;
  final VoidCallback onTap;
  final VoidCallback? onServerSourcesTap;

  const _TmdbAwareHistoryCard({
    required this.entry,
    required this.now,
    required this.imageHeaders,
    required this.onTap,
    this.onServerSourcesTap,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final sr = entry.source;
    final item = entry.item;
    final episode = formatEpisodeNumber(item.IndexNumber);
    final subtitle = episode != null
        ? '$episode · ${item.Name}'
        : (item.ProductionYear != null ? '${item.ProductionYear}' : null);

    final key = tmdbMediaKeyFrom(item);
    String? networkName;
    String? networkLogoUrl;
    if (key != null) {
      final enriched = ref.watch(tmdbEnrichedDetailProvider(key)).value;
      final network = enriched?.networks.firstOrNull;
      if (network != null) {
        networkName = network.name;
        final imageBase = ref
            .watch(tmdbSettingsProvider)
            .value
            ?.effectiveImageBaseUrl;
        if (imageBase != null) {
          networkLogoUrl = TmdbImageUrl.logo(
            imageBase,
            network.logoPath,
            size: 'w92',
          );
        }
      }
    }

    return AggregatedMediaCard(
      item: item,
      imageUrl: EmbyImageUrl.landscapeCard(
        baseUrl: sr.serverUrl,
        token: sr.token,
        item: item,
        maxWidth: 400,
      ),
      imageHeaders: imageHeaders,
      title: item.SeriesName ?? item.Name,
      subtitle: subtitle,
      serverName: sr.serverName,
      serverSourceCount: entry.sources.length,
      networkName: networkName,
      networkLogoUrl: networkLogoUrl,
      timeLabel: relativeTimeLabel(entry.dateFor(GroupAxis.watchedTime), now),
      showProgress: true,
      onTap: onTap,
      onServerSourcesTap: onServerSourcesTap,
    );
  }
}
