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

import '../core/contracts/trakt/trakt_calendar_entry.dart';
import '../core/domain/ports/tmdb_gateway.dart';
import '../shared/mappers/tmdb_image_url.dart';
import '../shared/utils/app_logger.dart';
import 'di_providers.dart';
import 'sm_account_provider.dart';
import 'tmdb_settings_provider.dart';

const _tag = 'TraktCalendar';

enum TraktCalendarFilter { all, shows, movies }

final traktCalendarFilterProvider = StateProvider<TraktCalendarFilter>(
  (ref) => TraktCalendarFilter.all,
);


final traktCalendarProvider = FutureProvider<List<TraktCalendarEntry>>((
  ref,
) async {
  final authed = ref.read(authedTraktGatewayProvider);
  if (!await authed.isConnected()) return const [];

  final filter = ref.watch(traktCalendarFilterProvider);

  final entries = <TraktCalendarEntry>[];

  if (filter != TraktCalendarFilter.movies) {
    try {
      final raw = await authed.fetchCalendarShows();
      entries.addAll(
        raw
            .map(TraktCalendarEntry.fromShowJson)
            .whereType<TraktCalendarEntry>(),
      );
    } catch (e) {
      AppLogger.warn(_tag, 'fetchCalendarShows failed', e);
    }
  }

  if (filter != TraktCalendarFilter.shows) {
    try {
      final raw = await authed.fetchCalendarMovies();
      entries.addAll(
        raw
            .map(TraktCalendarEntry.fromMovieJson)
            .whereType<TraktCalendarEntry>(),
      );
    } catch (e) {
      AppLogger.warn(_tag, 'fetchCalendarMovies failed', e);
    }
  }

  entries.sort((a, b) => a.firstAired.compareTo(b.firstAired));
  if (entries.isEmpty) return const [];

  final tmdbConfig = await _resolveTmdbConfig(ref);
  if (tmdbConfig == null) return entries;

  final gateway = ref.read(tmdbGatewayProvider);
  final cache = <String, _TmdbPosterResult>{};
  const enrichConcurrency = 5;
  final enriched = <TraktCalendarEntry>[];

  for (var i = 0; i < entries.length; i += enrichConcurrency) {
    final chunk = entries.skip(i).take(enrichConcurrency);
    final results = await Future.wait(
      chunk.map((entry) async {
        final id = entry.tmdbId;
        if (id == null || id <= 0) return entry;

        final cacheKey = '${entry.mediaType}:$id';
        try {
          final cached = cache[cacheKey] ??= await _fetchPoster(
            gateway,
            tmdbConfig.config,
            tmdbConfig.language,
            id.toString(),
            entry.mediaType,
          );
          var result = entry;
          if (cached.posterUrl != null) {
            result = result.withPoster(cached.posterUrl);
          }
          if (cached.title != null && cached.title!.isNotEmpty) {
            result = result.withTitle(cached.title!);
          }
          return result;
        } catch (_) {
          return entry;
        }
      }),
    );
    enriched.addAll(results);
  }

  return enriched;
});


Future<_TmdbReadyConfig?> _resolveTmdbConfig(Ref ref) async {
  final settings = await ref.watch(tmdbSettingsProvider.future);
  if (!settings.canRequest) return null;

  if (settings.accessMode == TmdbAccessMode.smAccount) {
    final account = await ref.watch(smAccountProvider.future);
    if (!account.canUseDataPlane) return null;
    final notifier = ref.read(smAccountProvider.notifier);
    final token = await notifier.getAccessToken();
    if (token == null) return null;
    return _TmdbReadyConfig(
      config: TmdbRequestConfig(
        apiBaseUrl: account.tmdbApiBaseUrl,
        imageBaseUrl: TmdbSettingsState.officialImageBaseUrl,
        accessToken: token,
        apiKey: '',
        onUnauthorized: () => notifier.getAccessToken(force: true),
      ),
      language: settings.language,
    );
  }

  return _TmdbReadyConfig(
    config: TmdbRequestConfig(
      apiBaseUrl: settings.effectiveApiBaseUrl,
      imageBaseUrl: settings.effectiveImageBaseUrl,
      accessToken: settings.effectiveAccessToken,
      apiKey: settings.effectiveApiKey,
    ),
    language: settings.language,
  );
}

Future<_TmdbPosterResult> _fetchPoster(
  TmdbGateway gateway,
  TmdbRequestConfig config,
  String language,
  String tmdbId,
  String mediaType,
) async {
  final detail = await gateway.getMediaDetail(
    config,
    tmdbId,
    mediaType,
    language: language,
  );
  final posterUrl = TmdbImageUrl.poster(
    config.imageBaseUrl,
    detail?.posterPath,
    size: 'w342',
  );
  return _TmdbPosterResult(posterUrl: posterUrl, title: detail?.title);
}

class _TmdbReadyConfig {
  final TmdbRequestConfig config;
  final String language;
  const _TmdbReadyConfig({required this.config, required this.language});
}

class _TmdbPosterResult {
  final String? posterUrl;
  final String? title;
  const _TmdbPosterResult({this.posterUrl, this.title});
}


List<CalendarDateGroup> groupByDate(List<TraktCalendarEntry> entries) {
  final now = DateTime.now();
  final today = DateTime(now.year, now.month, now.day);
  final groups = <String, List<TraktCalendarEntry>>{};
  final groupOrder = <String, DateTime>{};

  for (final e in entries) {
    final local = e.firstAired.toLocal();
    final date = DateTime(local.year, local.month, local.day);
    final label = _dateLabel(date, today);
    (groups[label] ??= []).add(e);
    groupOrder.putIfAbsent(label, () => date);
  }

  final sorted = groups.entries.toList()
    ..sort((a, b) => groupOrder[a.key]!.compareTo(groupOrder[b.key]!));

  return [
    for (final e in sorted) CalendarDateGroup(label: e.key, entries: e.value),
  ];
}

String _dateLabel(DateTime date, DateTime today) {
  final diff = date.difference(today).inDays;
  if (diff == 0) return '今天';
  if (diff == 1) return '明天';
  final weekday = _weekdayName(date.weekday);
  return '$weekday ${date.month}/${date.day}';
}

String _weekdayName(int weekday) => switch (weekday) {
  1 => '周一',
  2 => '周二',
  3 => '周三',
  4 => '周四',
  5 => '周五',
  6 => '周六',
  _ => '周日',
};

class CalendarDateGroup {
  final String label;
  final List<TraktCalendarEntry> entries;

  const CalendarDateGroup({required this.label, required this.entries});
}
