part of '../../core_app.dart';

/// 升级明细配置，对应升级接口中的 `upgrade` 字段。
class ShellUpgradeReleaseConfig {
  final String? versionName;
  final int? version;
  final int? isForce;
  final String? remark;
  final String? filePath;
  final int? fileSize;

  const ShellUpgradeReleaseConfig({
    this.versionName,
    this.version,
    this.isForce,
    this.remark,
    this.filePath,
    this.fileSize,
  });

  factory ShellUpgradeReleaseConfig.fromJson(Map<String, dynamic> json) {
    return ShellUpgradeReleaseConfig(
      versionName: _readShellUpgradeString(json['versionName']),
      version: _readShellUpgradeInt(json['version']),
      isForce: _readShellUpgradeInt(json['isForce']),
      remark: _readShellUpgradeString(json['remark']),
      filePath: _readShellUpgradeString(json['filePath']),
      fileSize: _readShellUpgradeInt(json['fileSize']),
    );
  }

  static ShellUpgradeReleaseConfig? fromDynamic(dynamic value) {
    if (value == null) {
      return null;
    }
    if (value is Map<String, dynamic>) {
      return ShellUpgradeReleaseConfig.fromJson(value);
    }
    if (value is Map) {
      return ShellUpgradeReleaseConfig.fromJson(
        Map<String, dynamic>.from(value),
      );
    }
    if (value is! String) {
      return null;
    }

    final trimmed = value.trim();
    if (trimmed.isEmpty) {
      return null;
    }

    try {
      final decoded = jsonDecode(trimmed);
      if (decoded is Map<String, dynamic>) {
        return ShellUpgradeReleaseConfig.fromJson(decoded);
      }
      if (decoded is Map) {
        return ShellUpgradeReleaseConfig.fromJson(
          Map<String, dynamic>.from(decoded),
        );
      }
    } catch (e) {
      debugPrint('解析 WebShell 升级配置 upgrade 字段异常: $e');
    }
    return null;
  }
}

/// 远程升级配置模型，对应顶层升级 JSON。
class ShellUpgradeConfig {
  final bool? success;
  final String? responseCode;
  final String? msg;
  final int? id;
  final int? upgradeFileId;
  final int? installFileId;
  final bool? isLatest;
  final int? appId;
  final String? releaseCode;
  final String? releaseTime;
  final bool? releaseStatus;
  final String? upgradeOssUrl;
  final bool? isAllowRollback;
  final String? rawConfig;
  final String? configDescribe;
  final int? describeType;
  final int? environment;
  final String? createdTime;
  final ShellUpgradeReleaseConfig? upgrade;
  final ShellBootstrapConfig? config;
  final String? installOssUrl;
  final String? describe;

  const ShellUpgradeConfig({
    this.success,
    this.responseCode,
    this.msg,
    this.id,
    this.upgradeFileId,
    this.installFileId,
    this.isLatest,
    this.appId,
    this.releaseCode,
    this.releaseTime,
    this.releaseStatus,
    this.upgradeOssUrl,
    this.isAllowRollback,
    this.rawConfig,
    this.configDescribe,
    this.describe,
    this.describeType,
    this.environment,
    this.createdTime,
    this.installOssUrl,
    this.upgrade,
    this.config,
  });

  String? get versionName => upgrade?.versionName;

  int? get version => upgrade?.version;

  int? get isForce => upgrade?.isForce;

  String? get remark => describe ?? upgrade?.remark;

  String? get filePath =>
      _readShellUpgradeString(installOssUrl ?? upgrade?.filePath);

  int? get fileSize => upgrade?.fileSize;

  bool get hasStructuredPayload => upgrade != null || config != null;

  bool shouldOfferUpgrade({required int localVersion}) {
    final remoteVersion = upgrade?.version;
    if (remoteVersion == null) {
      return false;
    }
    return remoteVersion > localVersion;
  }

  factory ShellUpgradeConfig.fromJson(Map<String, dynamic> json) {
    final data = json['data'];
    final dataMap = data is Map<String, dynamic>
        ? data
        : data is Map
        ? Map<String, dynamic>.from(data)
        : const <String, dynamic>{};
    final rawConfig = _readShellUpgradeString(dataMap['config']);
    final parsedConfig = _readShellUpgradeEmbeddedConfig(rawConfig);
    final rawBootstrapConfig = parsedConfig?['config'];
    final bootstrapConfig = rawBootstrapConfig is Map<String, dynamic>
        ? ShellBootstrapConfig.fromJson(rawBootstrapConfig)
        : rawBootstrapConfig is Map
        ? ShellBootstrapConfig.fromJson(
            Map<String, dynamic>.from(rawBootstrapConfig),
          )
        : null;

    return ShellUpgradeConfig(
      success: _readShellUpgradeBool(json['success']),
      responseCode: _readShellUpgradeString(json['code']),
      msg: _readShellUpgradeString(json['msg']),
      id: _readShellUpgradeInt(dataMap['id']),
      upgradeFileId: _readShellUpgradeInt(dataMap['upgradeFileId']),
      installFileId: _readShellUpgradeInt(dataMap['installFileId']),
      isLatest: _readShellUpgradeBool(dataMap['isLatest']),
      appId: _readShellUpgradeInt(dataMap['appId']),
      releaseCode: _readShellUpgradeString(dataMap['code']),
      releaseTime: _readShellUpgradeString(dataMap['releaseTime']),
      releaseStatus: _readShellUpgradeBool(dataMap['releaseStatus']),
      upgradeOssUrl: _readShellUpgradeString(dataMap['upgradeOssUrl']),
      installOssUrl: _readShellUpgradeString(dataMap['installOssUrl']),
      isAllowRollback: _readShellUpgradeBool(dataMap['isAllowRollback']),
      rawConfig: rawConfig,
      upgrade: ShellUpgradeReleaseConfig.fromDynamic(parsedConfig?['upgrade']),
      config: bootstrapConfig,
      configDescribe: _readShellUpgradeString(dataMap['configDescribe']),
      describe: _readShellUpgradeString(dataMap['describe']),
      describeType: _readShellUpgradeInt(dataMap['describeType']),
      environment: _readShellUpgradeInt(dataMap['environment']),
      createdTime: _readShellUpgradeString(dataMap['createdTime']),
    );
  }
}

String? _readShellUpgradeString(dynamic value) {
  final normalized = value?.toString().trim();
  if (normalized == null || normalized.isEmpty) {
    return null;
  }
  return normalized;
}

int? _readShellUpgradeInt(dynamic value) {
  if (value is int) {
    return value;
  }
  if (value is num) {
    return value.toInt();
  }
  final normalized = value?.toString().trim();
  if (normalized == null || normalized.isEmpty) {
    return null;
  }
  return int.tryParse(normalized);
}

bool? _readShellUpgradeBool(dynamic value) {
  if (value is bool) {
    return value;
  }
  if (value is num) {
    return value != 0;
  }
  final normalized = value?.toString().trim().toLowerCase();
  if (normalized == null || normalized.isEmpty) {
    return null;
  }
  if (normalized == 'true' || normalized == '1') {
    return true;
  }
  if (normalized == 'false' || normalized == '0') {
    return false;
  }
  return null;
}

Map<String, dynamic>? _readShellUpgradeEmbeddedConfig(String? value) {
  if (value == null || value.isEmpty) {
    return null;
  }

  try {
    final decoded = jsonDecode(value);
    if (decoded is Map<String, dynamic>) {
      return decoded;
    }
    if (decoded is Map) {
      return Map<String, dynamic>.from(decoded);
    }
  } catch (e) {
    debugPrint('解析 WebShell 升级配置 data.config 异常: $e');
  }
  return null;
}

/// 管理壳应用的升级逻辑，依赖 yx_app_upgrade_flutter。
class ShellUpgradeService {
  static final ShellUpgradeService instance = ShellUpgradeService._();
  ShellUpgradeService._();

  String? _configUrl;
  Future<int?> Function()? _localBuildNumberResolver;

  /// 注入升级配置地址。
  void setupConfigUrl(String? configUrl) {
    _configUrl = configUrl?.trim();
  }

  /// 检查应用版本，若配置中有更新则会弹出升级弹窗。
  Future<void> checkVersion(
    BuildContext context, {
    bool showNoUpdateToast = false,
  }) async {
    final configUrl = _configUrl;
    if (configUrl == null || configUrl.isEmpty) {
      if (showNoUpdateToast) {
        _showToastFromBridge(context, {'message': '未配置升级地址'});
      }
      return;
    }

    final remoteConfig = await _fetchConfig(configUrl);
    if (remoteConfig == null) {
      if (showNoUpdateToast) {
        _showToastFromBridge(context, {'message': '未获取到版本配置'});
      }
      return;
    }

    UpgradeAuxiliaryUtils.instance.initiateVersionCheck(
      // coverage:ignore-line
      context,
      showNoUpdateToast: showNoUpdateToast,
      future: _createVersionResolver(remoteConfig), // coverage:ignore-line
    );
  }

  Future<AppUpgradeVersion?> Function(int upType) _createVersionResolver(
    ShellUpgradeConfig remoteConfig,
  ) {
    return (int upType) async {
      final upgradeVersion = _convertToAppUpgradeVersion(remoteConfig);
      if (upgradeVersion == null) {
        return null;
      }

      final localBuildNumber = await _resolveLocalBuildNumber();
      if (localBuildNumber == null) {
        debugPrint('获取 WebShell 本地版本号失败，回退到升级插件内部版本比较');
        return upgradeVersion;
      }
      if (!remoteConfig.shouldOfferUpgrade(localVersion: localBuildNumber)) {
        debugPrint(
          'WebShell 远端版本(${remoteConfig.version}) <= 本地版本($localBuildNumber)，跳过升级提示',
        );
        return null;
      }
      return upgradeVersion;
    };
  }

  Future<int?> _resolveLocalBuildNumber() async {
    final resolver = _localBuildNumberResolver;
    if (resolver != null) {
      return resolver();
    }

    try {
      final appInfo = await AppUpgradePlugin().getAppInfo();
      return int.tryParse((appInfo['buildNumber'] ?? '').trim());
    } catch (e) {
      debugPrint('获取 WebShell 本地版本号异常: $e');
      return null;
    }
  }

  @visibleForTesting
  void debugSetLocalBuildNumberResolver(Future<int?> Function()? resolver) {
    _localBuildNumberResolver = resolver;
  }

  Future<ShellUpgradeConfig?> _fetchConfig(String url) async {
    try {
      final uri = Uri.tryParse(url);
      if (uri == null) {
        return null;
      }
      debugPrint('WebShell 正在获取升级配置: $url');
      final response = await http.get(uri).timeout(const Duration(seconds: 10));
      if (response.statusCode != 200) {
        debugPrint('获取 WebShell 升级配置失败，状态码=${response.statusCode}');
        return null;
      }
      final content = utf8.decode(response.bodyBytes);
      return _parseConfigString(content);
    } catch (e) {
      debugPrint('获取 WebShell 升级配置失败(网络异常/超时): $e');
      return null;
    }
  }

  ShellUpgradeConfig? _parseConfigString(String content) {
    try {
      final dynamic jsonMap = jsonDecode(content);
      if (jsonMap is Map<String, dynamic>) {
        final config = ShellUpgradeConfig.fromJson(jsonMap);
        return config.hasStructuredPayload ? config : null;
      }
      if (jsonMap is Map) {
        final config = ShellUpgradeConfig.fromJson(
          Map<String, dynamic>.from(jsonMap),
        );
        return config.hasStructuredPayload ? config : null;
      }
    } catch (e) {
      debugPrint('解析 WebShell 升级配置异常: $e');
    }
    return null;
  }

  /// 将远程 JSON 配置转换为升级弹窗所需的数据模型。
  AppUpgradeVersion? _convertToAppUpgradeVersion(ShellUpgradeConfig config) {
    if (config.version == null || config.versionName == null) {
      return null;
    }

    final apkSizeBytes = config.fileSize != null
        ? config.fileSize! * 1024
        : null;
    final filePath = (config.filePath != null && config.filePath!.isNotEmpty)
        ? config.filePath
        : null;

    return AppUpgradeVersion(
      versionName: config.versionName,
      versionBuildNumber: config.version,
      isForce: config.isForce == 1,
      updateContent: config.remark ?? '',
      downloadUrl: filePath,
      appStoreUrl: filePath,
      apkSize: apkSizeBytes,
      supportedMethods: [
        AppUpgradeMethod.browser,
        AppUpgradeMethod.inApp,
        AppUpgradeMethod.market,
      ],
    );
  }
}
