import 'dart:async';
import 'dart:convert';

import 'package:fjs/fjs.dart';
import 'package:flutter/foundation.dart' show visibleForTesting;
import 'package:flutter/services.dart' show rootBundle;

import '../../../shared/utils/app_logger.dart';
import '../../contracts/error.dart';
import '../../contracts/script_widget.dart';
import '../../domain/errors.dart';
import '../../domain/ports/script_widget_runtime.dart';
import 'widget_host_bridge.dart';

const String _tag = 'QuickJsWidgetRuntime';
const Duration _defaultInvocationTimeout = Duration(seconds: 30);
const int _maximumStackBytes = 1024 * 1024;
const int _maximumHeapBytes = 64 * 1024 * 1024;


const JsEvalOptions _scriptEvalOptions = JsEvalOptions.raw(
  global: true,
  strict: false,
  backtraceBarrier: false,
  promise: true,
);


class QuickJsWidgetRuntime implements ScriptWidgetRuntime {
  final WidgetHostBridge _hostBridge;
  final Duration invocationTimeout;
  final Map<String, _LoadedWidgetContext> _contexts =
      <String, _LoadedWidgetContext>{};

  QuickJsWidgetRuntime({
    WidgetHostBridge? hostBridge,
    this.invocationTimeout = _defaultInvocationTimeout,
  }) : _hostBridge = hostBridge ?? WidgetHostBridge(),
       assert(invocationTimeout > Duration.zero);

  @override
  Future<ScriptWidgetManifest> inspectWidget(String scriptSource) async {
    if (scriptSource.trim().isEmpty) {
      throw DomainError(ErrorCode.invalidParams, '模块脚本不能为空');
    }

    return _readManifest(scriptSource);
  }

  @override
  Future<ScriptWidgetManifest> loadWidget(String scriptSource) async {
    final manifest = await inspectWidget(scriptSource);

    final bootstrapData = await _hostBridge.loadBootstrapData(manifest.id);
    final engine = await _createEngine();
    try {
      await engine.init(
        bridge: (JsValue message) => _dispatchHostMessage(manifest.id, message),
      );
      await _installHostBootstrap(engine, bootstrapData);
      await _evaluateOrThrow(engine, scriptSource);
      final newContext = _LoadedWidgetContext(engine: engine);
      final existingContext = _contexts[manifest.id];
      _contexts[manifest.id] = newContext;
      existingContext?.dispose();
      return manifest;
    } catch (_) {
      unawaited(engine.close().catchError((_) {}));
      rethrow;
    }
  }

  @override
  bool isWidgetLoaded(String widgetId) => _contexts.containsKey(widgetId);

  Future<ScriptWidgetManifest> _readManifest(String scriptSource) async {
    final engine = await _createEngine();
    try {
      await engine.initWithoutBridge();
      await _evaluateOrThrow(
        engine,


        'var Widget = {}; '
        'var module = { exports: {} }; '
        'var exports = module.exports;',
      );
      await _evaluateOrThrow(engine, scriptSource);
      final metadataResult = await _evaluateOrThrow(
        engine,
        "typeof WidgetMetadata === 'object' && WidgetMetadata !== null "
        '? JSON.stringify(WidgetMetadata) : null',
      );
      final rawMetadata = metadataResult.value;
      final decodedMetadata = rawMetadata is String
          ? jsonDecode(rawMetadata)
          : null;
      if (decodedMetadata is! Map) {
        throw DomainError(ErrorCode.invalidParams, '脚本缺少有效的 WidgetMetadata 对象');
      }
      final manifest = ScriptWidgetManifest.fromJson(
        Map<String, dynamic>.from(decodedMetadata),
      );
      if (manifest.id.trim().isEmpty || manifest.title.trim().isEmpty) {
        throw DomainError(
          ErrorCode.invalidParams,
          'WidgetMetadata.id 和 title 不能为空',
        );
      }
      return manifest;
    } on DomainError {
      rethrow;
    } catch (error) {
      throw DomainError(ErrorCode.invalidParams, '无法解析模块元数据', details: error);
    } finally {
      unawaited(engine.close().catchError((_) {}));
    }
  }

  @override
  Future<Object?> invokeModuleFunction(
    String widgetId,
    String functionName,
    Map<String, Object?> params,
  ) async {
    final context = _contexts[widgetId];
    if (context == null) {
      throw DomainError(ErrorCode.storeNotFound, '模块尚未加载：$widgetId');
    }
    if (functionName.trim().isEmpty) {
      throw DomainError(ErrorCode.invalidParams, '模块函数名不能为空');
    }


    _drainBackgroundErrors(context.engine, widgetId);

    final encodedFunctionName = jsonEncode(functionName);
    final rawArgument = params['__forwardRawArgument'];
    final encodedArgument = rawArgument == null
        ? jsonEncode(params)
        : jsonEncode(rawArgument);
    final JsValue invocationResult;
    try {
      invocationResult = await context.engine
          .eval(
            source: JsCode.code('''
await (async function() {
  var functionName = $encodedFunctionName;
  var moduleFunction = globalThis[functionName];
  if (typeof moduleFunction !== 'function') {
    throw new Error('Module function not found: ' + functionName);
  }
  return await moduleFunction($encodedArgument);
})();
'''),
            options: _scriptEvalOptions,
          )
          .timeout(invocationTimeout);
    } on TimeoutException catch (error) {
      await unloadWidget(widgetId);
      throw _timeoutError(widgetId, functionName, error);
    } on DomainError {
      rethrow;
    } catch (error) {
      await unloadWidget(widgetId);
      throw _executionError(widgetId, functionName, error.toString());
    }
    return normalizeQuickJsValue(invocationResult.value);
  }

  DomainError _timeoutError(
    String widgetId,
    String functionName,
    Object error,
  ) {
    return DomainError(
      ErrorCode.serverTimeout,
      '模块执行超过 ${invocationTimeout.inSeconds} 秒，已终止运行环境',
      retryable: true,
      details: <String, Object?>{
        'widgetId': widgetId,
        'functionName': functionName,
        'error': error.toString(),
      },
    );
  }

  @override
  Future<void> unloadWidget(String widgetId) async {
    _contexts.remove(widgetId)?.dispose();
    _hostBridge.clearWidget(widgetId);
  }

  @override
  Future<void> dispose() async {
    final contexts = _contexts.values.toList(growable: false);
    _contexts.clear();
    for (final context in contexts) {
      context.dispose();
    }
    _hostBridge.clear();
  }

  Future<JsResult> _dispatchHostMessage(String widgetId, JsValue message) async {
    try {
      final decoded = message.value;
      final request = decoded is Map
          ? Map<String, dynamic>.from(decoded)
          : const <String, dynamic>{};
      final channel = request['channel']?.toString() ?? '';
      final rawPayload = request['payload'];
      final result = await _hostBridge.handleMessage(
        widgetId,
        channel,
        rawPayload is Map
            ? Map<String, dynamic>.from(rawPayload)
            : <String, dynamic>{},
      );
      return JsResult.ok(JsValue.from(result));
    } catch (error) {
      return JsResult.err(JsError.bridge(error.toString()));
    }
  }

  void _drainBackgroundErrors(JsEngine engine, String widgetId) {
    for (final backgroundError in engine.drainUnhandledJobErrors()) {
      AppLogger.warn(_tag, '[$widgetId] 脚本后台异步错误：$backgroundError');
    }
  }
}

Future<void>? _libFjsInit;

Future<void> _ensureLibFjsReady() {
  return _libFjsInit ??= LibFjs.init().then<void>(
    (_) {},
    onError: (Object error) {
      if (error is StateError) {


        return;
      }
      _libFjsInit = null;
      throw DomainError(
        ErrorCode.internalError,
        '脚本引擎初始化失败',
        details: error.toString(),
      );
    },
  );
}

Future<JsEngine> _createEngine() async {
  await _ensureLibFjsReady();
  return JsEngine.create(


    builtins: const JsBuiltinOptions(timers: true, url: true),
    runtimeOptions: JsEngineRuntimeOptions(
      memoryLimit: BigInt.from(_maximumHeapBytes),
      maxStackSize: BigInt.from(_maximumStackBytes),
    ),
  );
}

class _LoadedWidgetContext {
  final JsEngine engine;

  const _LoadedWidgetContext({required this.engine});

  void dispose() {


    unawaited(engine.close().catchError((_) {}));
  }
}

Future<JsValue> _evaluateOrThrow(JsEngine engine, String source) async {
  try {
    return await engine.eval(
      source: JsCode.code(source),
      options: _scriptEvalOptions,
    );
  } on DomainError {
    rethrow;
  } catch (error) {
    throw DomainError(
      ErrorCode.invalidParams,
      '模块脚本执行失败',
      details: error.toString(),
    );
  }
}


@visibleForTesting
Object? normalizeQuickJsValue(Object? value) {
  if (value is Map) {
    return <String, dynamic>{
      for (final entry in value.entries)
        entry.key?.toString() ?? '': normalizeQuickJsValue(entry.value),
    };
  }
  if (value is List) {
    return value.map(normalizeQuickJsValue).toList(growable: false);
  }
  return value;
}

DomainError _executionError(
  String widgetId,
  String functionName,
  String details,
) {
  return DomainError(
    ErrorCode.internalError,
    '模块函数执行失败：$functionName',
    details: <String, Object?>{
      'widgetId': widgetId,
      'functionName': functionName,
      'error': details,
    },
  );
}

Future<String>? _cheerioBundle;

Future<String> _cheerioBundleSource() =>
    _cheerioBundle ??= rootBundle.loadString('assets/js/cheerio.min.js');


Future<void> _installHostBootstrap(
  JsEngine engine,
  WidgetHostBootstrapData bootstrapData,
) async {
  final encodedBootData = jsonEncode(<String, Object?>{
    'storage': bootstrapData.storage,
    'sharedCache': bootstrapData.sharedCache,
  });
  await _evaluateOrThrow(
    engine,
    'globalThis.__widgetHostBootData = $encodedBootData;',
  );
  await _evaluateOrThrow(engine, await _cheerioBundleSource());
  await _evaluateOrThrow(engine, _kHostBootstrapJs);
}

const String _kHostBootstrapJs = r'''
(function() {
  var bootData = globalThis.__widgetHostBootData || {};
  var widgetStorageData = bootData.storage || {};
  var sharedCacheData = bootData.sharedCache || {};
  delete globalThis.__widgetHostBootData;

  if (typeof globalThis.module === 'undefined') {
    globalThis.module = { exports: {} };
  }
  if (typeof globalThis.exports === 'undefined') {
    globalThis.exports = globalThis.module.exports;
  }
  if (typeof globalThis.global === 'undefined') {
    globalThis.global = globalThis;
  }
  if (typeof globalThis.self === 'undefined') {
    globalThis.self = globalThis;
  }

  function hostCall(channel, payload) {
    return fjs.bridge_call({ channel: channel, payload: payload }).catch(
      function(rejection) {
        var message = (rejection instanceof Error)
          ? rejection.message
          : String(rejection != null ? rejection : 'unknown host error');
        throw new Error(message);
      }
    );
  }

  function hostNotify(channel, payload) {
    try {
      hostCall(channel, payload).catch(function() {});
    } catch (error) {}
  }

  function persistStorage() {
    hostNotify('WidgetStoragePersist', { storage: widgetStorageData });
  }

  function persistSharedCache() {
    hostNotify('WidgetSharedCachePersist', { sharedCache: sharedCacheData });
  }

  var cheerioModule = globalThis.__fjsCheerio;

  function loadHtml(html) {
    return cheerioModule.load(String(html == null ? '' : html));
  }

  var domEntries = {};
  var domNextHandle = 1;
  function domRegister(doc, node) {
    var handle = domNextHandle++;
    domEntries[handle] = { doc: doc, node: node };
    return handle;
  }

  function normalizeConsoleValue(value) {
    if (value instanceof Error) {
      return {
        __consoleErrorMarker: true,
        name: value.name || 'Error',
        message: value.message || String(value),
        stack: value.stack || ''
      };
    }
    return value;
  }
  function forwardConsole(level) {
    return function() {
      var values = [];
      for (var i = 0; i < arguments.length; i++) {
        values.push(normalizeConsoleValue(arguments[i]));
      }
      hostNotify('WidgetConsole', { level: level, values: values });
    };
  }
  globalThis.console = {
    log: forwardConsole('log'),
    info: forwardConsole('info'),
    debug: forwardConsole('debug'),
    warn: forwardConsole('warn'),
    error: forwardConsole('error'),
    trace: forwardConsole('debug')
  };

  globalThis.Widget = {
    http: {
      get: function(url, options) {
        return hostCall('WidgetHttp', { method: 'GET', url: url, options: options || {} });
      },
      post: function(url, body, options) {
        return hostCall('WidgetHttp', { method: 'POST', url: url, body: body, options: options || {} });
      },
      put: function(url, body, options) {
        return hostCall('WidgetHttp', { method: 'PUT', url: url, body: body, options: options || {} });
      },
      delete: function(url, options) {
        return hostCall('WidgetHttp', { method: 'DELETE', url: url, options: options || {} });
      }
    },
    storage: {
      get: function(key) { return widgetStorageData[key]; },
      set: function(key, value) { widgetStorageData[key] = value; persistStorage(); },
      remove: function(key) { delete widgetStorageData[key]; persistStorage(); },
      getItem: function(key) { return widgetStorageData[key]; },
      setItem: function(key, value) { widgetStorageData[key] = value; persistStorage(); },
      removeItem: function(key) { delete widgetStorageData[key]; persistStorage(); }
    },
    sharedCache: {
      get: function(namespace, key) {
        var namespaceData = sharedCacheData[namespace] || {};
        return namespaceData[key];
      },
      set: function(namespace, key, value) {
        if (!sharedCacheData[namespace]) sharedCacheData[namespace] = {};
        sharedCacheData[namespace][key] = value;
        persistSharedCache();
      },
      remove: function(namespace, key) {
        if (sharedCacheData[namespace]) delete sharedCacheData[namespace][key];
        persistSharedCache();
      }
    },
    html: { load: loadHtml },
    dom: {
      parse: function(html) {
        try {
          return domRegister(loadHtml(html), null);
        } catch (error) {
          return null;
        }
      },
      select: function(handle, selector) {
        var entry = domEntries[handle];
        if (!entry || !selector) return [];
        try {
          var matched = entry.node == null
            ? entry.doc(selector)
            : entry.doc(entry.node).find(selector);
          return matched.toArray().map(function(node) {
            return domRegister(entry.doc, node);
          });
        } catch (error) {
          return [];
        }
      },
      text: function(handle) {
        var entry = domEntries[handle];
        if (!entry) return '';
        return entry.node == null
          ? entry.doc('body').text()
          : entry.doc(entry.node).text();
      },
      html: function(handle) {
        var entry = domEntries[handle];
        if (!entry) return '';
        var value = entry.node == null
          ? entry.doc.html()
          : entry.doc(entry.node).html();
        return value == null ? '' : value;
      },
      attr: function(handle, name) {
        var entry = domEntries[handle];
        if (!entry || entry.node == null) return null;
        var value = entry.doc(entry.node).attr(String(name));
        return value == null ? null : value;
      }
    }
  };
})();
''';
