import 'dart:convert';

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

/// Dio interceptor that logs request / response in JSON friendly format.
class LoggerInterceptor extends Interceptor {
  LoggerInterceptor({
    this.prettyJson = true,
    this.chunkSize = 800,
    this.enableChunking = true,
  });

  /// Pretty print JSON instead of single-line.
  final bool prettyJson;

  /// Maximum size of each log chunk when [enableChunking] is true.
  final int chunkSize;

  /// Split large logs into chunks to avoid truncation.
  final bool enableChunking;

  final Logger _logger = Logger(
    printer: PrettyPrinter(
      methodCount: 0,
      lineLength: 120,
      printTime: true,
      colors: true,
    ),
  );

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    final uri = options.uri;
    final buffer = StringBuffer()
      ..writeln('🚀 ${options.method} $uri')
      ..writeln('Headers: ${_maskHeaders(options.headers)}');

    if (options.queryParameters.isNotEmpty) {
      buffer.writeln('Query: ${_asReadableJson(options.queryParameters)}');
    }

    _log(Level.info, 'REQUEST', uri.toString(), buffer.toString());

    if (options.data != null) {
      _logBody(Level.info, 'REQUEST BODY', uri.toString(), options.data);
    }

    final curl = _buildCurlCommand(options);
    _log(Level.info, 'CURL', uri.toString(), curl);

    handler.next(options);
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    final uri = response.requestOptions.uri;
    final statusCode = response.statusCode ?? 0;

    final buffer = StringBuffer()
      ..writeln('✅ $statusCode ${response.requestOptions.method} $uri');

    // if (response.headers.map.isNotEmpty) {
    //   buffer.writeln('Resp Headers: ${_asReadableJson(response.headers.map)}');
    // }

    _log(Level.debug, 'RESPONSE', uri.toString(), buffer.toString());
    _logBody(Level.debug, 'RESPONSE BODY', uri.toString(), response.data);

    handler.next(response);
  }

  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    final uri = err.requestOptions.uri;
    final statusCode = err.response?.statusCode;
    final buffer = StringBuffer()
      ..writeln('❌ ${statusCode ?? 'Unknown'} ${err.requestOptions.method} $uri')
      ..writeln('Error: ${err.message}');

    _log(Level.error, 'ERROR', uri.toString(), buffer.toString());

    if (err.response?.data != null) {
      _logBody(Level.error, 'ERROR BODY', uri.toString(), err.response?.data);
    }

    handler.next(err);
  }

  String _maskHeaders(Map<String, dynamic> headers) {
    final sanitized = _sanitizeHeaders(headers);
    return sanitized.toString();
  }

  void _logBody(Level level, String phase, String uri, dynamic data) {
    final body = _asReadableJson(data);
    _log(level, phase, uri, body);
  }

  String _asReadableJson(dynamic data) {
    if (data == null) return 'null';

    dynamic resolved = data;

    if (data is String) {
      final trimmed = data.trim();
      if (_looksLikeJson(trimmed)) {
        resolved = _tryDecode(trimmed) ?? data;
      } else {
        return data;
      }
    }

    try {
      if (resolved is String) {
        return resolved;
      }
      final encoder = prettyJson
          ? const JsonEncoder.withIndent('  ')
          : const JsonEncoder();
      return encoder.convert(resolved);
    } catch (_) {
      return resolved.toString();
    }
  }

  dynamic _tryDecode(String text) {
    try {
      return jsonDecode(text);
    } catch (_) {
      return null;
    }
  }

  bool _looksLikeJson(String text) {
    return (text.startsWith('{') && text.endsWith('}')) ||
        (text.startsWith('[') && text.endsWith(']'));
  }

  void _log(Level level, String phase, String uri, String message) {
    if (!enableChunking || message.length <= chunkSize) {
      _logger.log(level, '[$phase] $uri\n$message');
      return;
    }

    final total = (message.length / chunkSize).ceil();
    var index = 0;
    for (var i = 0; i < message.length; i += chunkSize) {
      final end = (i + chunkSize < message.length) ? i + chunkSize : message.length;
      index += 1;
      _logger.log(
        level,
        '[$phase PART $index/$total] $uri\n${message.substring(i, end)}',
      );
    }
  }

  Map<String, dynamic> _sanitizeHeaders(Map<String, dynamic> headers) {
    final filtered = Map<String, dynamic>.from(headers);
    const sensitive = {'authorization', 'cookie', 'set-cookie'};
    filtered.updateAll((key, value) {
      if (sensitive.contains(key.toLowerCase())) {
        return '***';
      }
      return value;
    });
    return filtered;
  }

  String _buildCurlCommand(RequestOptions options) {
    final sanitizedHeaders = _sanitizeHeaders(options.headers);
    final buffer = StringBuffer('curl -X ${options.method}');
    sanitizedHeaders.forEach((key, value) {
      if (value == null) return;
      buffer.write(" -H '${_escapeSingleQuotes('$key: $value')}'");
    });
    if (options.data != null) {
      final payload = _payloadForCurl(options.data);
      buffer.write(" --data '${_escapeSingleQuotes(payload)}'");
    }
    buffer.write(" '${options.uri}'");
    return buffer.toString();
  }

  String _payloadForCurl(dynamic data) {
    if (data == null) return '';
    if (data is String) return data;
    try {
      return jsonEncode(data);
    } catch (_) {
      return data.toString();
    }
  }

  String _escapeSingleQuotes(String value) => value.replaceAll("'", r"'\''");
}
