// The plugin manager is a singleton class that manages the plugins.
// 1. It merge metadata and the desc of plugins.

import 'dart:convert';
import 'dart:collection';
import 'package:flutter/material.dart';

const String kValueTrue = '1';
const String kValueFalse = '0';

class ConfigItem {
  String key;
  String description;
  String defaultValue;

  ConfigItem(this.key, this.defaultValue, this.description);
  ConfigItem.fromJson(Map<String, dynamic> json)
      : key = json['key'] ?? '',
        description = json['description'] ?? '',
        defaultValue = json['default'] ?? '';

  static String get trueValue => kValueTrue;
  static String get falseValue => kValueFalse;
  static bool isTrue(String value) => value == kValueTrue;
  static bool isFalse(String value) => value == kValueFalse;
}

class UiType {
  String key;
  String text;
  String tooltip;
  String action;

  UiType(this.key, this.text, this.tooltip, this.action);

  UiType.fromJson(Map<String, dynamic> json)
      : key = json['key'] ?? '',
        text = json['text'] ?? '',
        tooltip = json['tooltip'] ?? '',
        action = json['action'] ?? '';

  static UiType? create(Map<String, dynamic> json) {
    if (json['t'] == 'Button') {
      return UiButton.fromJson(json['c']);
    } else if (json['t'] == 'Checkbox') {
      return UiCheckbox.fromJson(json['c']);
    } else {
      return null;
    }
  }
}

class UiButton extends UiType {
  String icon;

  UiButton(
      {required String key,
      required String text,
      required this.icon,
      required String tooltip,
      required String action})
      : super(key, text, tooltip, action);

  UiButton.fromJson(Map<String, dynamic> json)
      : icon = json['icon'] ?? '',
        super.fromJson(json);
}

class UiCheckbox extends UiType {
  UiCheckbox(
      {required String key,
      required String text,
      required String tooltip,
      required String action})
      : super(key, text, tooltip, action);

  UiCheckbox.fromJson(Map<String, dynamic> json) : super.fromJson(json);
}

class Location {
  // location key:
  //  host|main|settings|plugin
  //  client|remote|toolbar|display
  HashMap<String, UiType> ui;

  Location(this.ui);
  Location.fromJson(Map<String, dynamic> json) : ui = HashMap() {
    (json['ui'] as Map<String, dynamic>).forEach((key, value) {
      var ui = UiType.create(value);
      if (ui != null) {
        this.ui[ui.key] = ui;
      }
    });
  }
}

class PublishInfo {
  PublishInfo({
    required this.lastReleased,
    required this.published,
  });

  final DateTime lastReleased;
  final DateTime published;
}

class Meta {
  Meta({
    required this.id,
    required this.name,
    required this.version,
    required this.description,
    required this.author,
    required this.home,
    required this.license,
    required this.publishInfo,
    required this.source,
  });

  final String id;
  final String name;
  final String version;
  final String description;
  final String author;
  final String home;
  final String license;
  final PublishInfo publishInfo;
  final String source;
}

class SourceInfo {
  String name; // 1. RustDesk github 2. Local
  String url;
  String description;

  SourceInfo({
    required this.name,
    required this.url,
    required this.description,
  });
}

class PluginInfo with ChangeNotifier {
  SourceInfo sourceInfo;
  Meta meta;
  String installedVersion; // It is empty if not installed.
  String failedMsg;
  String invalidReason; // It is empty if valid.

  PluginInfo({
    required this.sourceInfo,
    required this.meta,
    required this.installedVersion,
    required this.invalidReason,
    this.failedMsg = '',
  });

  bool get installed => installedVersion.isNotEmpty;
  bool get needUpdate => installed && installedVersion != meta.version;

  void setInstall(String msg) {
    if (msg == "finished") {
      msg = '';
    }
    failedMsg = msg;
    if (msg.isEmpty) {
      installedVersion = meta.version;
    }
    notifyListeners();
  }

  void setUninstall(String msg) {
    failedMsg = msg;
    if (msg.isEmpty) {
      installedVersion = '';
    }
    notifyListeners();
  }
}

class PluginManager with ChangeNotifier {
  String failedReason = ''; // The reason of failed to load plugins.
  final List<PluginInfo> _plugins = [];

  PluginManager._();
  static final PluginManager _instance = PluginManager._();
  static PluginManager get instance => _instance;

  List<PluginInfo> get plugins => _plugins;

  PluginInfo? getPlugin(String id) {
    for (var p in _plugins) {
      if (p.meta.id == id) {
        return p;
      }
    }
    return null;
  }

  void handleEvent(Map<String, dynamic> evt) {
    if (evt['plugin_list'] != null) {
      _handlePluginList(evt['plugin_list']);
    } else if (evt['plugin_install'] != null && evt['id'] != null) {
      _handlePluginInstall(evt['id'], evt['plugin_install']);
    } else if (evt['plugin_uninstall'] != null && evt['id'] != null) {
      _handlePluginUninstall(evt['id'], evt['plugin_uninstall']);
    } else {
      debugPrint('Failed to handle manager event: $evt');
    }
  }

  void _sortPlugins() {
    plugins.sort((a, b) {
      if (a.installed) {
        return -1;
      } else if (b.installed) {
        return 1;
      } else {
        return 0;
      }
    });
  }

  void _handlePluginList(String pluginList) {
    _plugins.clear();
    try {
      for (var p in json.decode(pluginList) as List<dynamic>) {
        final plugin = _getPluginFromEvent(p);
        if (plugin == null) {
          continue;
        }
        _plugins.add(plugin);
      }
    } catch (e) {
      debugPrint('Failed to decode $e,  plugin list \'$pluginList\'');
    }
    _sortPlugins();
    notifyListeners();
  }

  void _handlePluginInstall(String id, String msg) {
    debugPrint('Plugin \'$id\' install msg $msg');
    for (var i = 0; i < _plugins.length; i++) {
      if (_plugins[i].meta.id == id) {
        _plugins[i].setInstall(msg);
        _sortPlugins();
        notifyListeners();
        return;
      }
    }
  }

  void _handlePluginUninstall(String id, String msg) {
    debugPrint('Plugin \'$id\' uninstall msg $msg');
    for (var i = 0; i < _plugins.length; i++) {
      if (_plugins[i].meta.id == id) {
        _plugins[i].setUninstall(msg);
        _sortPlugins();
        notifyListeners();
        return;
      }
    }
  }

  PluginInfo? _getPluginFromEvent(Map<String, dynamic> evt) {
    final s = evt['source'];
    assert(s != null, 'Source is null');
    if (s == null) {
      return null;
    }
    final source = SourceInfo(
      name: s['name'],
      url: s['url'] ?? '',
      description: s['description'] ?? '',
    );

    final m = evt['meta'];
    assert(m != null, 'Meta is null');
    if (m == null) {
      return null;
    }

    late DateTime lastReleased;
    late DateTime published;
    try {
      lastReleased = DateTime.parse(
          m['publish_info']?['last_released'] ?? '1970-01-01T00+00:00');
    } catch (e) {
      lastReleased = DateTime.utc(1970);
    }
    try {
      published = DateTime.parse(
          m['publish_info']?['published'] ?? '1970-01-01T00+00:00');
    } catch (e) {
      published = DateTime.utc(1970);
    }

    final meta = Meta(
      id: m['id'],
      name: m['name'],
      version: m['version'],
      description: m['description'] ?? '',
      author: m['author'],
      home: m['home'] ?? '',
      license: m['license'] ?? '',
      source: m['source'] ?? '',
      publishInfo:
          PublishInfo(lastReleased: lastReleased, published: published),
    );
    return PluginInfo(
      sourceInfo: source,
      meta: meta,
      installedVersion: evt['installed_version'],
      invalidReason: evt['invalid_reason'] ?? '',
    );
  }
}

PluginManager get pluginManager => PluginManager.instance;