import 'dart:io';

import 'package:dio/dio.dart';
import 'package:networks_sdk/src/data/datasources/http/api_client.dart';
import 'package:networks_sdk/src/data/dto/api_requestable.dart';
import 'package:networks_sdk/src/domain/entities/api_error.dart';
import 'package:networks_sdk/src/domain/entities/api_request_type.dart';
import 'package:networks_sdk/src/presentation/wiring/api_config.dart';
import 'package:networks_sdk/src/presentation/wiring/network_callbacks.dart';
import '../../../networks_sdk_platform_interface.dart';
import '../../domain/entities/http_method.dart';

/// 网络层数据源
///
/// 封装 [ApiClient]，提供两种请求入口：
/// - [executeRequest] — 统一请求入口（标准 / Upload / 流式）
/// - [executeDownload] — 带进度的文件下载（支持断点续传）
///
/// 流式（SSE）请求也走 [executeRequest]，由业务 Request 类 override
/// `decodeResponse` 处理 SSE 解析。SDK 内部根据
/// `requestType == ApiRequestType.stream` 自动切换 `ResponseType.plain`。
class NetworksSdkMethodChannelDataSource {
  final NetworksSdkPlatform platform;

  late ApiClient apiClient;

  NetworksSdkMethodChannelDataSource(this.platform);

  Future<String?> getPlatformVersion() async {
    return await platform.getPlatformVersion();
  }

  void initialize(ApiConfig apiConfig) {
    apiClient = ApiClient(config: apiConfig);
  }

  // ══════════════════════════════════════════════════════════════════════════
  //  统一请求入口
  // ══════════════════════════════════════════════════════════════════════════

  /// 执行 API 请求 — 统一入口
  ///
  /// 支持三种请求类型，由 `request.requestType` 控制行为：
  /// - `request` / `login` — 标准 JSON 请求
  /// - `upload` — 文件上传（FormData / 二进制）
  /// - `stream` — SSE / chunked，内部用 `ResponseType.plain` 获取原始文本，
  ///   由业务 Request 类 override `decodeResponse` 处理 SSE 解析
  ///
  /// 流程：网络前置检查 → 构建 URL → 设置元数据 → 执行请求
  /// → 响应变换（可选，stream 类型跳过）→ 解码响应 → 错误映射
  ///
  /// 拦截器负责：header 注入、加密/解密、Token 刷新重试、业务错误拦截、日志
  ///
  /// Upload 类型支持两种模式：
  /// - 自有后端上传：path 为相对路径，自动拼接 baseURL
  /// - S3 presigned URL：path 以 http 开头，直接使用全路径
  Future<T?> executeRequest<T>(
    ApiRequestable<T> request, {
    CancelToken? cancelToken,
  }) async {
    // 在首个 await 前捕获调用栈，async 间隙后栈信息会丢失
    final callerFrame = _callerFrame(StackTrace.current);

    await _checkNetwork(request.path);

    try {
      final isUpload = request.requestType == ApiRequestType.upload;
      final isStream = request.requestType == ApiRequestType.stream;
      final path = request.path;
      final url = (isUpload && path.startsWith('http'))
          ? path
          : '${apiClient.config.baseURL}$path';

      final options = Options(
        method: request.method.value,
        // 流式请求用 plain，Dio 返回原始文本，由 decodeResponse 解析 SSE
        responseType: isStream ? ResponseType.plain : null,
        extra: {
          'requestType': request.requestType,
          'includeToken': request.includeToken,
          'customHeaders': request.customHeaders,
          '_requestClass': request.runtimeType.toString(),
          '_callerFrame': callerFrame,
        },
      );

      // 访问 parameters 触发代码生成器的 fromJson 注册
      final params = request.parameters;

      final isGet = request.method == HttpMethod.get;
      final response = await apiClient.dio.request(
        url,
        data: isUpload ? request.uploadData : (isGet ? null : params),
        queryParameters: isGet ? params : null,
        options: options,
        cancelToken: cancelToken,
      );

      // 响应变换：stream 类型由 decodeResponse 自行处理，不做变换
      if (!isStream) {
        _applyResponseTransform(response);
      }

      return request.decodeResponse(response);
    } on DioException catch (e) {
      throw apiClient.mapDioError(e);
    } on ApiError {
      rethrow;
    } catch (e) {
      // decodeResponse() overrides may intentionally throw business exceptions
      // (e.g. SecondaryPasscodeRequiredException on code 30164).
      // JSON decoding FormatExceptions are already caught inside decodeResponse()
      // and wrapped as ApiError.decodingError before reaching here.
      // Re-throw so callers can handle typed business exceptions.
      rethrow;
    }
  }

  // ══════════════════════════════════════════════════════════════════════════
  //  文件下载
  // ══════════════════════════════════════════════════════════════════════════

  /// 下载文件到本地路径
  ///
  /// 支持进度回调和断点续传（通过 HTTP Range header 实现）。
  ///
  /// 非续传模式直接用 Dio.download（高效，内部流式写入）。
  /// 续传模式用 stream + FileMode.append，因为 Dio.download 始终从
  /// 文件头部写入，无法正确追加到已下载部分之后。
  ///
  /// [url] — 下载 URL（完整路径或相对路径，相对路径自动拼接 baseURL）
  /// [savePath] — 本地保存路径
  /// [onProgress] — 下载进度回调
  /// [cancelToken] — 取消令牌
  /// [resume] — 是否断点续传（文件已存在时从断点继续下载）
  /// [headers] — 额外请求头
  Future<void> executeDownload({
    required String url,
    required String savePath,
    OnDownloadProgress? onProgress,
    CancelToken? cancelToken,
    bool resume = false,
    Map<String, String>? headers,
  }) async {
    await _checkNetwork(url);

    try {
      final fullUrl = url.startsWith('http')
          ? url
          : '${apiClient.config.baseURL}$url';

      final extraHeaders = <String, String>{};
      if (headers != null) extraHeaders.addAll(headers);

      // 断点续传：读取已下载部分的大小，设置 Range header
      int startBytes = 0;
      if (resume) {
        final file = File(savePath);
        if (file.existsSync()) {
          startBytes = file.lengthSync();
          extraHeaders['Range'] = 'bytes=$startBytes-';
        }
      }

      if (resume && startBytes > 0) {
        // 续传模式：stream + append，确保新数据追加到文件末尾
        await _downloadWithResume(
          url: fullUrl,
          savePath: savePath,
          startBytes: startBytes,
          headers: extraHeaders,
          onProgress: onProgress,
          cancelToken: cancelToken,
        );
      } else {
        // 普通下载：Dio.download（高效，内部流式写入）
        await apiClient.dio.download(
          fullUrl,
          savePath,
          cancelToken: cancelToken,
          deleteOnError: true,
          options: Options(
            headers: extraHeaders.isEmpty ? null : extraHeaders,
            extra: {
              'requestType': ApiRequestType.download,
              'includeToken': true,
            },
          ),
          onReceiveProgress: onProgress != null
              ? (received, total) => onProgress(received, total)
              : null,
        );
      }
    } on DioException catch (e) {
      throw apiClient.mapDioError(e);
    } on ApiError {
      rethrow;
    } catch (e) {
      throw ApiError.unknown(e.toString());
    }
  }

  /// 断点续传下载：stream 响应 + FileMode.append
  ///
  /// Dio.download 内部用 FileMode.write（从头覆盖），无法正确续传。
  /// 这里手动读流并追加写入文件。
  ///
  /// 如果服务端不支持 Range 请求（返回 200 而非 206），
  /// 自动回退为覆盖写入，防止文件损坏。
  Future<void> _downloadWithResume({
    required String url,
    required String savePath,
    required int startBytes,
    required Map<String, String> headers,
    OnDownloadProgress? onProgress,
    CancelToken? cancelToken,
  }) async {
    final response = await apiClient.dio.get<ResponseBody>(
      url,
      cancelToken: cancelToken,
      options: Options(
        responseType: ResponseType.stream,
        headers: headers.isEmpty ? null : headers,
        extra: {'requestType': ApiRequestType.download, 'includeToken': true},
      ),
    );

    final stream = response.data?.stream;
    if (stream == null) return;

    // 检查服务端是否接受了 Range 请求
    // 206 = 支持续传，追加写入
    // 200 = 不支持 Range，返回完整文件，需要覆盖写入
    final isPartialContent = response.statusCode == 206;
    final effectiveStartBytes = isPartialContent ? startBytes : 0;

    if (!isPartialContent) {
      apiClient.config.onLog?.call(
        'Server does not support Range, falling back to full download',
        tag: 'Download',
      );
    }

    // Content-Length 是本次传输量
    final contentLength =
        int.tryParse(response.headers.value('content-length') ?? '') ?? -1;
    final totalBytes = contentLength > 0
        ? contentLength + effectiveStartBytes
        : -1;

    final file = File(savePath);
    // 不支持续传时用 write 覆盖，支持时用 append 追加
    final fileMode = isPartialContent
        ? FileMode.writeOnlyAppend
        : FileMode.writeOnly;
    final raf = file.openSync(mode: fileMode);
    int received = effectiveStartBytes;

    try {
      await for (final chunk in stream) {
        raf.writeFromSync(chunk);
        received += chunk.length;
        onProgress?.call(received, totalBytes);
      }
    } finally {
      raf.closeSync();
    }
  }

  // ══════════════════════════════════════════════════════════════════════════
  //  内部辅助
  // ══════════════════════════════════════════════════════════════════════════

  /// 网络前置检查，不可用时直接抛 [ApiError.noNetworkConnection]
  Future<void> _checkNetwork(String path) async {
    if (apiClient.config.onCheckNetworkAvailable != null) {
      final available = await apiClient.config.onCheckNetworkAvailable!();
      if (!available) {
        apiClient.config.onLog?.call(
          'Network unavailable, abort request: $path',
          tag: 'ApiClient',
        );
        throw const ApiError.noNetworkConnection();
      }
    }
  }

  /// 从调用栈提取 App 层第一个帧（文件名:行号），用于日志定位
  ///
  /// 仅 debug 模式栈帧可读；release 模式返回空字符串（由日志层静默忽略）。
  static String _callerFrame(StackTrace stack) {
    try {
      for (final line in stack.toString().split('\n')) {
        // 沿用已验证有效的过滤条件，不改动
        if (line.isEmpty ||
            line.contains('package:networks_sdk') ||
            line.contains('package:dio') ||
            line.contains('(dart:') ||   // dart: 内部帧格式是 (dart:async/...)，不能用 'dart:' 否则会误杀 .dart:LINE
            line.contains('<asynchronous')) {
          continue;
        }
        // 用简单 regex 匹配 (URI:LINE:COL)，已被证明可在 Android 上正常工作
        final fileMatch = RegExp(r'\((.+):(\d+):\d+\)').firstMatch(line);
        if (fileMatch == null) { continue; }

        final fileName = fileMatch.group(1)!.split('/').last;
        final lineNum = fileMatch.group(2)!;

        // 从括号前的文本里取最后一个单词作为 Symbol（可能带 #N 前缀）
        final before = line.substring(0, fileMatch.start).trim();
        final symbolMatch = RegExp(r'(\S+)$').firstMatch(before);
        final symbol = symbolMatch?.group(1) ?? '';

        return symbol.isNotEmpty ? '$symbol · $fileName:$lineNum' : '$fileName:$lineNum';
      }
    } catch (_) {}
    return '';
  }

  /// 应用响应变换（如果 App 层注入了 onTransformResponse）
  void _applyResponseTransform(Response response) {
    final transform = apiClient.config.onTransformResponse;
    if (transform == null) return;
    if (response.data is! Map<String, dynamic>) return;

    final transformed = transform(response.data as Map<String, dynamic>);
    if (transformed != null) {
      response.data = transformed;
    }
  }
}
