import 'dart:io';

import 'package:dio/dio.dart';
import 'package:dio/io.dart';

import '../../contracts/danmaku.dart';
import '../../domain/ports/danmaku_gateway.dart';
import '../emby_api/emby_headers.dart';
import '../../../shared/utils/app_logger.dart';

const _tag = 'Danmaku';


String buildDanmakuFileName({
  required String name,
  required String? type,
  required String? seriesName,
  required int? season,
  required int? episode,
}) {
  if (type == 'Episode' && seriesName != null && seriesName.isNotEmpty) {
    if (season != null && episode != null) {
      return '$seriesName S${season}E$episode';
    }
    AppLogger.warn(
      _tag,
      'buildDanmakuFileName: missing index fields '
      '(season=$season, episode=$episode) for "$seriesName / $name"; '
      'fallback to raw name',
    );
  }
  return name;
}

class DanmakuApiClient implements DanmakuGateway {
  DanmakuApiClient({Dio? dio}) : _dio = dio ?? _buildDefaultDio();

  final Dio _dio;

  static Dio _buildDefaultDio() {
    final dio = Dio(
      BaseOptions(
        connectTimeout: const Duration(seconds: 4),
        receiveTimeout: const Duration(seconds: 8),
        headers: {
          'User-Agent': EmbyRequestHeaders.userAgent,
          'Accept': 'application/json, text/plain, */*',
          'accept-language': 'zh-CN,en,*',
        },
      ),
    );
    dio.httpClientAdapter = IOHttpClientAdapter(
      createHttpClient: () =>
          HttpClient()..idleTimeout = const Duration(seconds: 1),
    );
    return dio;
  }

  static const _matchConnectTimeout = Duration(seconds: 8);
  static const _matchReceiveTimeout = Duration(seconds: 8);

  static const _commentReceiveTimeout = Duration(seconds: 30);

  @override
  Future<List<DanmakuMatch>> match(
    String baseUrl,
    String fileName,
    int durationSec,
  ) async {
    try {
      final resp = await _dio.post<Map<String, dynamic>>(
        '$baseUrl/api/v2/match',
        data: {
          'fileHash': '',
          'fileName': fileName,
          'fileSize': 0,
          'matchMode': 'fileName',
          'videoDuration': durationSec,
        },
        options: Options(
          contentType: 'application/json',
          sendTimeout: _matchConnectTimeout,
          receiveTimeout: _matchReceiveTimeout,
        ),
      );
      final data = resp.data;
      if (data == null) {
        AppLogger.warn(_tag, 'match($fileName) null response body');
        return [];
      }
      if (data['success'] != true || data['isMatched'] != true) {
        AppLogger.debug(
          _tag,
          'match($fileName) no result: success=${data['success']} '
          'isMatched=${data['isMatched']}',
        );
        return [];
      }
      final list = (data['matches'] as List)
          .map((e) => DanmakuMatch.fromJson(e as Map<String, dynamic>))
          .toList();
      AppLogger.debug(_tag, 'match($fileName) → ${list.length} entries');
      return list;
    } catch (e) {
      AppLogger.warn(_tag, 'match($fileName) failed', e);
      rethrow;
    }
  }

  @override
  Future<List<DanmakuMatch>> searchEpisodes(
    String baseUrl,
    String anime,
    int? episode,
  ) async {
    try {
      final resp = await _dio.get<Map<String, dynamic>>(
        '$baseUrl/api/v2/search/episodes',
        queryParameters: {
          'anime': anime,
          if (episode != null) 'episode': episode,
        },
        options: Options(receiveTimeout: _matchReceiveTimeout),
      );
      final data = resp.data;
      if (data == null || data['success'] != true) {
        AppLogger.debug(
          _tag,
          'searchEpisodes($anime) no result: success=${data?['success']}',
        );
        return [];
      }
      final animes = data['animes'];
      if (animes is! List) return [];
      final out = <DanmakuMatch>[];
      for (final a in animes.whereType<Map<String, dynamic>>()) {
        final animeId = (a['animeId'] as num?)?.toInt() ?? 0;
        final animeTitle = a['animeTitle']?.toString() ?? '';
        final eps = a['episodes'];
        if (eps is! List) continue;
        for (final e in eps.whereType<Map<String, dynamic>>()) {
          final epId = (e['episodeId'] as num?)?.toInt();
          if (epId == null) continue;
          out.add(
            DanmakuMatch(
              episodeId: epId,
              animeId: animeId,
              animeTitle: animeTitle,
              episodeTitle: e['episodeTitle']?.toString() ?? '',
            ),
          );
        }
      }
      AppLogger.debug(
        _tag,
        'searchEpisodes($anime ep=$episode) → ${out.length} entries',
      );
      return out;
    } catch (e) {
      AppLogger.warn(_tag, 'searchEpisodes($anime) failed', e);
      rethrow;
    }
  }

  @override
  Future<List<DanmakuBangumiEpisode>> getBangumiEpisodes(
    String baseUrl,
    int animeId,
  ) async {
    if (animeId <= 0) return [];
    try {
      final resp = await _dio.get<Map<String, dynamic>>(
        '$baseUrl/api/v2/bangumi/$animeId',
      );
      final data = resp.data;
      if (data == null || data['success'] != true) return [];
      final bangumi = data['bangumi'];
      if (bangumi is! Map<String, dynamic>) return [];
      final episodes = bangumi['episodes'];
      if (episodes is! List) return [];
      return episodes
          .whereType<Map<String, dynamic>>()
          .map(DanmakuBangumiEpisode.fromJson)
          .toList();
    } catch (e) {
      AppLogger.warn(_tag, 'getBangumiEpisodes($animeId) failed', e);
      return [];
    }
  }

  @override
  Future<List<DanmakuComment>> fetchComments(
    String baseUrl,
    int episodeId, {
    int chConvert = 0,
  }) async {
    try {
      final resp = await _dio.get<Map<String, dynamic>>(
        '$baseUrl/api/v2/comment/$episodeId'
        '?format=json&chConvert=$chConvert&withRelated=true',
        options: Options(receiveTimeout: _commentReceiveTimeout),
      );
      final data = resp.data;
      if (data == null) {
        AppLogger.warn(_tag, 'fetchComments($episodeId) null response body');
        return [];
      }
      final list = (data['comments'] as List? ?? []).map((e) {
        final m = e as Map<String, dynamic>;
        return DanmakuComment.fromRaw(
          (m['cid'] as num).toInt(),
          m['p'] as String,
          m['m'] as String,
        );
      }).toList();
      AppLogger.debug(
        _tag,
        'fetchComments($episodeId) → ${list.length} comments',
      );
      return list;
    } catch (e) {
      AppLogger.warn(_tag, 'fetchComments($episodeId) failed', e);
      rethrow;
    }
  }
}
