/*
* @message: 日志拦截器
* @Author: Jack
* @Email: Jack@163.com
* @Date: 2020-06-18 19:47:32
*/
import 'package:dio/dio.dart';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
// 必须引入此库处理 Emoji 截断问题
import 'package:characters/characters.dart';

/// description: Dio 日志拦截器
class SCDioLogger extends Interceptor {
  final bool request;
  final bool requestHeader;
  final bool requestBody;
  final bool responseBody;
  final bool responseHeader;
  final bool error;
  static const int initialTab = 1;
  static const String tabStep = '    ';
  final bool compact;
  final int maxWidth;

  /// Log printer; defaults debugPrint to console.
  void Function(Object object) logPrint;
  final bool enableLog;

  SCDioLogger({
    this.enableLog = kDebugMode,
    this.request = true,
    this.requestHeader = true,
    this.requestBody = true,
    this.responseHeader = false,
    this.responseBody = true,
    this.error = true,
    this.maxWidth = 90,
    this.compact = true,
    this.logPrint = print, // 修复：在 iOS 上 debugPrint 比 print 更稳健
  });

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    if (!enableLog) return handler.next(options);

    if (request) _pReqHdr(options);
    if (requestHeader) {
      _pTable(options.queryParameters, header: 'Query Parameters');
      final requestHeaders = <String, dynamic>{};
      requestHeaders.addAll(options.headers);
      requestHeaders['contentType'] = options.contentType?.toString();
      requestHeaders['responseType'] = options.responseType?.toString();
      requestHeaders['followRedirects'] = options.followRedirects;
      requestHeaders['connectTimeout'] = options.connectTimeout;
      requestHeaders['receiveTimeout'] = options.receiveTimeout;
      _pTable(requestHeaders, header: 'Headers');
      _pTable(options.extra, header: 'Extras');
    }
    if (requestBody && options.method != 'GET') {
      final data = options.data;
      if (data != null) {
        if (data is Map) {
          _pTable(data, header: 'Body');
        } else if (data is FormData) {
          final formDataMap = Map()
            ..addEntries(data.fields)
            ..addEntries(data.files);
          _pTable(formDataMap, header: 'Form data | ${data.boundary}');
        } else {
          _pBlock(data.toString());
        }
      }
    }
    handler.next(options);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    if (!enableLog) return handler.next(err);

    if (error) {
      if (err.type == DioExceptionType.badResponse) {
        final uri = err.response?.requestOptions.uri;
        _pBox(
            header: 'DioError ║ Status: ${err.response?.statusCode} ${err.response?.statusMessage}',
            text: uri.toString());
        if (err.response?.data != null) {
          logPrint('╔ ${err.type.toString()}');
          _pResp(err.response!);
        }
        _pLine('╚');
      } else {
        final uri = err.requestOptions.uri;
        _pBox(header: 'DioError ║ ${err.type} ║ ${uri.toString()}', text: err.message);
      }
    }
    handler.next(err);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    if (!enableLog) return handler.next(response);

    if (responseHeader) {
      final responseHeaders = <String, String>{};
      response.headers.forEach((k, list) => responseHeaders[k] = list.toString());
      _pTable(responseHeaders, header: 'Headers');
    }

    if (responseBody) {
      _pRespHdr(response);
      logPrint('╔ Body');
      logPrint('║');
      _pResp(response);
      logPrint('║');
      _pLine('╚');
    }
    handler.next(response);
  }

  void _pBox({String? header, String? text}) {
    logPrint('');
    logPrint('╔╣ $header');
    logPrint('║  $text');
    _pLine('╚');
  }

  void _pResp(Response response) {
    if (response.data != null) {
      if (response.data is Map) {
        _pMap(response.data);
      } else if (response.data is List) {
        logPrint('║${_ind()}[');
        _pList(response.data);
        logPrint('║${_ind()}]');
      } else {
        _pBlock(response.data.toString());
      }
    }
  }

  void _pRespHdr(Response response) {
    final uri = response.requestOptions.uri;
    final method = response.requestOptions.method;
    String header = 'Response ║ $method ║ Status: ${response.statusCode} ${response.statusMessage}';
    logPrint('╔╣ $header ${'═' * 20}');
    logPrint('║  ${uri.toString()}');
    logPrint('║  ');
  }

  void _pReqHdr(RequestOptions options) {
    final uri = options.uri;
    final method = options.method;
    _pBox(header: 'Request ║ $method ', text: uri.toString());
  }

  void _pLine([String pre = '', String suf = '╝']) => logPrint('$pre${'═' * maxWidth}');

  void _pKV(String key, Object v) {
    final pre = '╟ $key: ';
    final msg = v.toString();

    if (pre.length + msg.length > maxWidth) {
      logPrint(pre);
      _pBlock(msg);
    } else {
      logPrint('$pre$msg');
    }
  }

  // 修复：使用 characters 处理截断，防止 Emoji 损坏
  void _pBlock(String msg) {
    final charData = msg.characters;
    int lines = (charData.length / maxWidth).ceil();
    for (int i = 0; i < lines; ++i) {
      final start = i * maxWidth;
      final end = math.min<int>(start + maxWidth, charData.length);
      logPrint('║ ' + charData.getRange(start, end).toString());
    }
  }

  String _ind([int tabCount = initialTab]) => tabStep * tabCount;

  void _pMap(Map data, {int tabs = initialTab, bool isListItem = false, bool isLast = false}) {
    final bool isRoot = tabs == initialTab;
    final initialIndent = _ind(tabs);
    tabs++;

    if (isRoot || isListItem) logPrint('║$initialIndent{');

    data.keys.toList().asMap().forEach((index, key) {
      final isLast = index == data.length - 1;
      var value = data[key];
      if (value is String) value = '\"$value\"';

      if (value is Map) {
        if (compact && _flatMap(value)) {
          logPrint('║${_ind(tabs)} $key: $value${!isLast ? ',' : ''}');
        } else {
          logPrint('║${_ind(tabs)} $key: {');
          _pMap(value, tabs: tabs);
        }
      } else if (value is List) {
        if (compact && _flatList(value)) {
          logPrint('║${_ind(tabs)} $key: ${value.toString()}');
        } else {
          logPrint('║${_ind(tabs)} $key: [');
          _pList(value, tabs: tabs);
          logPrint('║${_ind(tabs)} ]${isLast ? '' : ','}');
        }
      } else {
        final msg = value.toString().replaceAll('\n', '');
        final indent = _ind(tabs);
        final charMsg = msg.characters;
        final linWidth = maxWidth - indent.length;

        // 修复：字符串分段时处理 Emoji
        if (charMsg.length + indent.length > maxWidth) {
          int lines = (charMsg.length / linWidth).ceil();
          for (int i = 0; i < lines; ++i) {
            final start = i * linWidth;
            final end = math.min<int>(start + linWidth, charMsg.length);
            logPrint('║${_ind(tabs)} ${charMsg.getRange(start, end).toString()}');
          }
        } else {
          logPrint('║${_ind(tabs)} $key: $msg${!isLast ? ',' : ''}');
        }
      }
    });

    logPrint('║$initialIndent}${isListItem && !isLast ? ',' : ''}');
  }

  void _pList(List list, {int tabs = initialTab}) {
    list.asMap().forEach((i, e) {
      final isLast = i == list.length - 1;
      if (e is Map) {
        if (compact && _flatMap(e)) {
          logPrint('║${_ind(tabs)}  $e${!isLast ? ',' : ''}');
        } else {
          _pMap(e, tabs: tabs + 1, isListItem: true, isLast: isLast);
        }
      } else {
        logPrint('║${_ind(tabs + 2)} $e${isLast ? '' : ','}');
      }
    });
  }

  bool _flatMap(Map map) {
    return map.values.where((val) => val is Map || val is List).isEmpty &&
        map.toString().length < maxWidth;
  }

  bool _flatList(List list) {
    return (list.length < 10 && list.toString().length < maxWidth);
  }

  void _pTable(Map? map, {String? header}) {
    if (map == null || map.isEmpty) return;
    logPrint('╔ $header ');
    map.forEach((key, value) {
      _pKV(key.toString(), (value ?? 'null').toString());
    });
    _pLine('╚');
  }
}