import '../../contracts/danmaku.dart';
import '../../domain/ports/danmaku_gateway.dart';
import '../../domain/usecases/script_widget/script_widget_service.dart';

class ScriptWidgetDanmakuGateway implements DanmakuGateway {
  final Future<ScriptWidgetService> Function() loadService;

  const ScriptWidgetDanmakuGateway({required this.loadService});

  @override
  Future<List<DanmakuMatch>> match(
    String baseUrl,
    String fileName,
    int durationSec,
  ) async {
    final parsedContext = _parseFileName(fileName);
    return _searchAndExpand(
      baseUrl,
      title: parsedContext.title,
      type: parsedContext.episode == null ? 'movie' : 'tv',
      season: parsedContext.season,
      episode: parsedContext.episode,
      durationSec: durationSec,
    );
  }

  @override
  Future<List<DanmakuMatch>> searchEpisodes(
    String baseUrl,
    String anime,
    int? episode,
  ) {
    return _searchAndExpand(
      baseUrl,
      title: anime,
      type: 'tv',
      episode: episode,
      durationSec: 0,
    );
  }

  Future<List<DanmakuMatch>> _searchAndExpand(
    String sourceUrl, {
    required String title,
    required String type,
    required int durationSec,
    int? season,
    int? episode,
  }) async {
    final service = await loadService();
    final widgetId = _widgetIdFromSource(sourceUrl);
    final rawSearch = await service.invoke(widgetId, 'searchDanmu', {
      'title': title,
      'seriesName': type == 'tv' ? title : null,
      'type': type,
      'season': season?.toString(),
      'episode': episode?.toString(),
      'runtime': durationSec,
    });
    final searchMap = _asMap(rawSearch);
    final rawAnimes = searchMap['animes'];
    if (rawAnimes is! List) return const <DanmakuMatch>[];

    final matches = <DanmakuMatch>[];
    for (final rawAnime in rawAnimes.whereType<Map>()) {
      final anime = Map<String, dynamic>.from(rawAnime);
      final animeId = _intValue(anime['animeId'] ?? anime['id']);
      if (animeId == null) continue;
      final animeTitle =
          anime['animeTitle']?.toString() ??
          anime['title']?.toString() ??
          title;
      final episodes = await getBangumiEpisodes(sourceUrl, animeId);
      for (final candidate in episodes) {
        final candidateNumber =
            int.tryParse(candidate.episodeNumber) ??
            _episodeNumberFromTitle(candidate.episodeTitle);
        if (episode != null && candidateNumber != episode) continue;
        matches.add(
          DanmakuMatch(
            episodeId: candidate.episodeId,
            animeId: animeId,
            animeTitle: animeTitle,
            episodeTitle: candidate.episodeTitle,
          ),
        );
      }
    }
    return matches;
  }

  @override
  Future<List<DanmakuComment>> fetchComments(
    String baseUrl,
    int episodeId, {
    int chConvert = 0,
  }) async {
    final service = await loadService();
    final rawResult = await service.invoke(
      _widgetIdFromSource(baseUrl),
      'getComments',
      <String, Object?>{'commentId': episodeId, 'chConvert': chConvert},
    );
    final resultMap = _asMap(rawResult);
    final rawComments = resultMap['comments'] ?? rawResult;
    if (rawComments is! List) return const <DanmakuComment>[];
    final comments = <DanmakuComment>[];
    for (final rawComment in rawComments.whereType<Map>()) {
      final comment = Map<String, dynamic>.from(rawComment);
      final cid = _intValue(comment['cid']) ?? comments.length;
      final parameter = comment['p']?.toString();
      final message = comment['m']?.toString();
      if (parameter == null || message == null) continue;
      comments.add(DanmakuComment.fromRaw(cid, parameter, message));
    }
    return comments;
  }

  @override
  Future<List<DanmakuBangumiEpisode>> getBangumiEpisodes(
    String baseUrl,
    int animeId,
  ) async {
    final service = await loadService();
    final rawResult = await service.invoke(
      _widgetIdFromSource(baseUrl),
      'getDetail',
      <String, Object?>{'animeId': animeId},
    );
    final rawEpisodes = _asMap(rawResult)['episodes'] ?? rawResult;
    if (rawEpisodes is! List) return const <DanmakuBangumiEpisode>[];
    final episodes = <DanmakuBangumiEpisode>[];
    for (final rawEpisode in rawEpisodes.whereType<Map>()) {
      try {
        episodes.add(
          DanmakuBangumiEpisode.fromJson(Map<String, dynamic>.from(rawEpisode)),
        );
      } catch (_) {}
    }
    return episodes;
  }
}

String _widgetIdFromSource(String sourceUrl) => Uri.parse(sourceUrl).host;

({String title, int? season, int? episode}) _parseFileName(String fileName) {
  final match = RegExp(
    r'^(.*?)\s+S(\d+)E(\d+)\s*$',
    caseSensitive: false,
  ).firstMatch(fileName.trim());
  if (match == null) {
    return (title: fileName.trim(), season: null, episode: null);
  }
  return (
    title: match.group(1)?.trim() ?? fileName.trim(),
    season: int.tryParse(match.group(2) ?? ''),
    episode: int.tryParse(match.group(3) ?? ''),
  );
}

int? _episodeNumberFromTitle(String title) {
  final match = RegExp(r'(?:第\s*)?(\d+)(?:\s*[集话]|$)').firstMatch(title);
  return int.tryParse(match?.group(1) ?? '');
}

Map<String, dynamic> _asMap(Object? value) {
  if (value is Map<String, dynamic>) return value;
  if (value is Map) return Map<String, dynamic>.from(value);
  return <String, dynamic>{};
}

int? _intValue(Object? value) {
  if (value is num) return value.toInt();
  return int.tryParse(value?.toString() ?? '');
}
