/*
 * Copyright 2023 Hongen Wang All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import 'dart:collection';
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_desktop_context_menu/flutter_desktop_context_menu.dart';
import 'package:flutter_toastr/flutter_toastr.dart';
import 'package:proxypin/l10n/app_localizations.dart';
import 'package:proxypin/network/bin/configuration.dart';
import 'package:proxypin/network/bin/server.dart';
import 'package:proxypin/network/channel/channel.dart';
import 'package:proxypin/network/channel/channel_context.dart';
import 'package:proxypin/network/channel/host_port.dart';
import 'package:proxypin/network/components/host_filter.dart';
import 'package:proxypin/network/http/http.dart';
import 'package:proxypin/network/http/http_client.dart';
import 'package:proxypin/ui/component/multi_select_controller.dart';
import 'package:proxypin/ui/component/transition.dart';
import 'package:proxypin/ui/component/utils.dart';
import 'package:proxypin/ui/content/panel.dart';
import 'package:proxypin/ui/desktop/request/request.dart';
import 'package:proxypin/utils/har.dart';
import 'package:proxypin/utils/keyword_highlight.dart';
import 'package:proxypin/utils/lang.dart';
import 'package:proxypin/utils/listenable_list.dart';

import '../../component/model/search_model.dart';

/// 左侧域名
/// @author wanghongen
/// 2023/10/8
class DomainList extends StatefulWidget {
  final ProxyServer proxyServer;
  final NetworkTabController panel;

  final ListenableList<HttpRequest> list;
  final bool shrinkWrap;
  final Function(List<HttpRequest>)? onRemove;
  final MultiSelectController selectionController;
  final RequestSelectionHandlers selectionHandlers;

  const DomainList(
      {super.key,
      required this.proxyServer,
      required this.list,
      this.shrinkWrap = true,
      required this.panel,
      this.onRemove,
      required this.selectionController,
      required this.selectionHandlers});

  @override
  State<StatefulWidget> createState() {
    return DomainWidgetState();
  }
}

class DomainWidgetState extends State<DomainList> with AutomaticKeepAliveClientMixin {
  //域名和对应请求列表的映射
  final LinkedHashMap<String, DomainRequests> containerMap = LinkedHashMap<String, DomainRequests>();

  //搜索视图
  LinkedHashMap<String, DomainRequests> searchView = LinkedHashMap<String, DomainRequests>();

  //搜索的内容
  SearchModel? searchModel;
  bool changing = false; //是否存在刷新任务
  //关键词高亮监听
  late VoidCallback highlightListener;
  late MultiSelectListener<String> selectionListener;

  bool sortDesc = true;

  AppLocalizations get localizations => AppLocalizations.of(context)!;

  MultiSelectController get selectionController => widget.selectionController;

  void changeState() {
    if (!changing) {
      changing = true;
      Future.delayed(const Duration(milliseconds: 500), () {
        setState(() {
          changing = false;
        });
      });
    }
  }

  @override
  void initState() {
    super.initState();
    var container = widget.list;
    for (var request in container.source) {
      DomainRequests domainRequests = getDomainRequests(request);
      domainRequests.addRequest(request.requestId, request, sortDesc);
    }
    highlightListener = () {
      //回调时机在高亮设置页面dispose之后。所以需要在下一帧刷新，否则会报错
      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        highlightHandler();
      });
    };
    KeywordHighlights.addListener(highlightListener);

    selectionListener = MultiSelectListener((items) {
      if (!mounted) {
        return;
      }
      _refreshRequestSelection(items);
    });
    selectionController.selectedIds.addListener(selectionListener);
  }

  @override
  void dispose() {
    selectionController.selectedIds.removeListener(selectionListener);
    KeywordHighlights.removeListener(highlightListener);
    super.dispose();
  }

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    var list = containerMap.values;

    //根究搜素文本过滤
    if (searchModel?.isNotEmpty == true) {
      searchView = searchFilter(searchModel!);
      list = searchView.values;
      selectionController.prune(list.expand((e) => e.body).map((e) => e.request.requestId).toSet());
    } else {
      searchView.clear();
    }

    return widget.shrinkWrap
        ? SingleChildScrollView(child: Column(children: list.toList()))
        : ListView.builder(itemCount: list.length, itemBuilder: (_, index) => list.elementAt(index));
  }

  ///搜索
  void search(SearchModel? val) {
    setState(() {
      searchModel = val;
    });
  }

  ///搜索过滤
  LinkedHashMap<String, DomainRequests> searchFilter(SearchModel searchModel) {
    LinkedHashMap<String, DomainRequests> result = LinkedHashMap<String, DomainRequests>();

    containerMap.forEach((key, domainRequests) {
      var body = domainRequests.search(searchModel);
      if (body.isNotEmpty) {
        result[key] = domainRequests.copy(body: body, selected: searchView[key]?.currentSelected);
      }
    });

    return result;
  }

  ///高亮处理
  void highlightHandler() {
    //获取所有请求Widget
    List<RequestWidget> requests = containerMap.values.map((e) => e.body).expand((element) => element).toList();
    for (RequestWidget request in requests) {
      GlobalKey key = request.key as GlobalKey<State>;
      key.currentState?.setState(() {});
    }
  }

  ///添加请求
  void add(Channel channel, HttpRequest request) {
    String? host = request.remoteDomain();
    if (host == null) {
      return;
    }

    //按照域名分类
    DomainRequests domainRequests = getDomainRequests(request);
    var isNew = domainRequests.body.isEmpty;

    domainRequests.addRequest(request.requestId, request, sortDesc);
    //搜索视图
    if (searchModel?.isNotEmpty == true && searchModel?.filter(request, null) == true) {
      searchView[host]?.addRequest(request.requestId, request, sortDesc);
    }

    if (isNew) {
      setState(() {
        containerMap[host] = domainRequests;
      });
    }
  }

  DomainRequests getDomainRequests(HttpRequest request) {
    var host = request.remoteDomain()!;
    DomainRequests? domainRequests = containerMap[host];
    if (domainRequests == null) {
      domainRequests = DomainRequests(
        host,
        proxyServer: widget.proxyServer,
        trailing: appIcon(request),
        onDelete: deleteHost,
        onExportHar: exportDomainHar,
        onRequestRemove: (req) {
          widget.onRemove?.call([req]);
          changeState();
        },
        selectionController: selectionController,
        selectionHandlers: widget.selectionHandlers,
      );
      containerMap[host] = domainRequests;
    }

    return domainRequests;
  }

  Widget? appIcon(HttpRequest request) {
    var processInfo = request.processInfo;
    if (processInfo == null) {
      return null;
    }

    return futureWidget(
        processInfo.getIcon(),
        (data) =>
            data.isEmpty ? const SizedBox() : Image.memory(data, width: 23, height: Platform.isWindows ? 16 : null));
  }

  ///移除域名
  void deleteHost(String host) {
    DomainRequests? domainRequests = containerMap.remove(host);
    if (domainRequests == null) {
      return;
    }
    setState(() {});

    widget.onRemove?.call(domainRequests.body.map((e) => e.request).toList());
  }

  ///添加响应
  void addResponse(ChannelContext channelContext, HttpResponse response) {
    String domain = response.request?.hostAndPort?.domain ?? channelContext.host!.domain;
    DomainRequests? domainRequests = containerMap[domain];
    var pathRow = domainRequests?.getRequest(response);
    pathRow?.setResponse(response);
    if (pathRow == null) {
      return;
    }

    //搜索视图
    if (searchModel?.isNotEmpty == true && searchModel?.filter(pathRow.request, response) == true) {
      var requests = searchView[domain];
      if (requests?.getRequest(response) == null) {
        requests?.addRequest(response.requestId, pathRow.request, sortDesc);
      }
      requests?.getRequest(response)?.setResponse(response);
    }
  }

  void remove(List<HttpRequest> list) {
    for (var request in list) {
      String? host = request.remoteDomain();
      containerMap[host]?._removeRequest(request);
    }
  }

  ///清理
  void clean() {
    setState(() {
      containerMap.clear();
      searchView.clear();

      var container = widget.list;
      for (var request in container.source) {
        DomainRequests domainRequests = getDomainRequests(request);
        domainRequests.addRequest(request.requestId, request, sortDesc);
      }
    });
  }

  List<HttpRequest> currentView() {
    var container = containerMap.values;
    if (searchModel?.isNotEmpty == true) {
      container = searchView.values;
    }
    return container.expand((list) => list.body.map((it) => it.request)).toList();
  }

  Future<void> exportDomainHar(String domain) async {
    var requests = containerMap[domain]?.body.map((it) => it.request).toList() ?? [];
    if (requests.isEmpty) {
      if (mounted) FlutterToastr.show(localizations.emptyData, context);
      return;
    }

    var fileName = _domainHarFileName(domain);
    var path = await FilePicker.platform.saveFile(fileName: fileName);
    if (path == null) {
      return;
    }

    try {
      var file = await File(path).create(recursive: true);
      await Har.writeFile(requests, file, title: fileName);
      if (mounted) FlutterToastr.show(localizations.exportSuccess, context);
    } catch (e) {
      if (mounted) FlutterToastr.show('${localizations.exportFailed} $e', context);
    }
  }

  String _domainHarFileName(String domain) {
    var uri = Uri.tryParse(domain);
    var host = (uri?.host.isNotEmpty == true) ? uri!.host : domain;
    var suffix = uri?.hasPort == true ? '_${uri!.port}' : '';
    var safeDomain = '$host$suffix'.replaceAll(RegExp(r'[^A-Za-z0-9._-]'), '_');
    if (safeDomain.isEmpty) {
      safeDomain = 'domain';
    }
    return 'ProxyPin_${safeDomain}_${DateTime.now().dateFormat()}.har';
  }

  ///排序
  void sort(bool desc) {
    sortDesc = desc;
    containerMap.forEach((key, request) {
      var reversed = request.body.toList().reversed;
      request.body.clear();
      request.body.addAll(reversed);
      request.changeState();
    });
  }

  List<HttpRequest> selectedRequests() {
    final selectedIds = selectionController.selectedIds;
    if (selectedIds.isEmpty) {
      return [];
    }
    return currentView().where((request) => selectedIds.contains(request.requestId)).toList();
  }

  void selectRange(HttpRequest request) {
    final currentIds = currentView().map((item) => item.requestId).toList();
    if (currentIds.isEmpty) {
      return;
    }

    selectionController.selectRange(currentIds, request.requestId);
  }

  void _refreshRequestSelection(List<String> selectedIds) {
    var container = containerMap.values;
    if (searchModel?.isNotEmpty == true) {
      container = searchView.values;
    }
    for (var domain in container) {
      for (var requestWidget in domain.body) {
        if (selectedIds.contains(requestWidget.request.requestId)) {
          requestWidget.changeState();
        }
      }
    }
  }
}

///标题和内容布局 标题是域名 内容是域名下请求
class DomainRequests extends StatefulWidget {
  //请求ID和请求的映射
  final Map<String, RequestWidget> requestMap = HashMap<String, RequestWidget>();

  final String domain;
  final ProxyServer proxyServer;
  final Widget? trailing;

  //请求列表
  final Queue<RequestWidget> body = Queue();

  //是否选中
  final bool selected;

  //移除回调
  final Function(String host)? onDelete;
  final Function(String host)? onExportHar;
  final Function(HttpRequest request)? onRequestRemove;
  final RequestSelectionHandlers selectionHandlers;
  final MultiSelectController selectionController;

  DomainRequests(this.domain,
      {this.selected = false,
      this.onDelete,
      this.onExportHar,
      required this.proxyServer,
      this.onRequestRemove,
      required this.selectionHandlers,
      this.trailing,
      required this.selectionController})
      : super(key: GlobalKey<_DomainRequestsState>());

  ///添加请求
  void addRequest(String? requestId, HttpRequest request, bool sortDesc) {
    if (requestMap.containsKey(requestId)) return;

    var requestWidget = RequestWidget(request,
        index: body.length,
        proxyServer: proxyServer,
        displayDomain: false,
        multiSelectController: selectionController,
        selectionHandlers: selectionHandlers,
        remove: (it) => _remove(it));
    sortDesc ? body.addFirst(requestWidget) : body.addLast(requestWidget);

    if (requestId == null) {
      return;
    }

    requestMap[requestId] = requestWidget;
    changeState();
  }

  RequestWidget? getRequest(HttpResponse response) {
    return requestMap[response.request?.requestId ?? response.requestId];
  }

  void setTrailing(Widget? trailing) {
    var state = key as GlobalKey<_DomainRequestsState>;
    state.currentState?.trailing = trailing;
  }

  void _remove(RequestWidget requestWidget) {
    if (body.remove(requestWidget)) {
      onRequestRemove?.call(requestWidget.request);
      changeState();
    }
  }

  void _removeRequest(HttpRequest request) {
    var requestWidget = requestMap.remove(request.requestId);
    if (requestWidget != null) {
      _remove(requestWidget);
    }
  }

  ///根据文本过滤
  Iterable<RequestWidget> search(SearchModel searchModel) {
    return body
        .where((element) => searchModel.filter(element.request, element.response.get() ?? element.request.response));
  }

  ///复制
  DomainRequests copy({Iterable<RequestWidget>? body, bool? selected}) {
    var state = key as GlobalKey<_DomainRequestsState>;
    var headerBody = DomainRequests(domain,
        trailing: trailing,
        selected: selected ?? state.currentState?.selected == true,
        onDelete: onDelete,
        onExportHar: onExportHar,
        onRequestRemove: onRequestRemove,
        selectionController: selectionController,
        selectionHandlers: selectionHandlers,
        proxyServer: proxyServer);
    if (body != null) {
      headerBody.body.addAll(body);
    }
    return headerBody;
  }

  bool get currentSelected {
    var state = key as GlobalKey<_DomainRequestsState>;
    return state.currentState?.selected == true;
  }

  void changeState() {
    var state = key as GlobalKey<_DomainRequestsState>;
    state.currentState?.changeState();
  }

  @override
  State<StatefulWidget> createState() {
    return _DomainRequestsState();
  }
}

class _DomainRequestsState extends State<DomainRequests> {
  final GlobalKey<ColorTransitionState> transitionState = GlobalKey<ColorTransitionState>();
  late Configuration configuration;
  late bool selected;
  Widget? trailing;
  bool changing = false;

  AppLocalizations get localizations => AppLocalizations.of(context)!;

  @override
  void initState() {
    super.initState();
    configuration = widget.proxyServer.configuration;
    selected = widget.selected;
    trailing = widget.trailing;
  }

  void changeState() {
    //防止频繁刷新
    if (!changing) {
      changing = true;
      Future.delayed(const Duration(milliseconds: 500), () {
        if (mounted) {
          setState(() {
            changing = false;
          });
          transitionState.currentState?.show();
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      _hostWidget(widget.domain),
      Offstage(offstage: !selected, child: Column(children: widget.body.toList()))
    ]);
  }

  //domain title
  Widget _hostWidget(String title) {
    var host = GestureDetector(
        onSecondaryTap: menu,
        child: ListTile(
            minLeadingWidth: 25,
            leading: Icon(selected ? Icons.arrow_drop_down : Icons.arrow_right, size: 18),
            trailing: trailing,
            dense: true,
            horizontalTitleGap: 0,
            contentPadding: const EdgeInsets.only(left: 3, right: 8),
            visualDensity: const VisualDensity(vertical: -3.6),
            title: Text(title,
                textAlign: TextAlign.left,
                style: const TextStyle(fontSize: 14),
                maxLines: 1,
                overflow: TextOverflow.ellipsis),
            onTap: () {
              setState(() {
                selected = !selected;
              });
            }));

    return ColorTransition(
        key: transitionState,
        duration: const Duration(milliseconds: 1800),
        begin: Theme.of(context).focusColor,
        startAnimation: false,
        child: host);
  }

  //域名右键菜单
  void menu() {
    Menu menu = Menu(items: [
      MenuItem(
          label: localizations.copyHost,
          onClick: (_) {
            Clipboard.setData(ClipboardData(text: Uri.parse(widget.domain).host));
            FlutterToastr.show(localizations.copied, context);
          }),
      MenuItem.separator(),
      MenuItem(
        label: localizations.domainFilter,
        type: 'submenu',
        submenu: hostFilterMenu(),
      ),
      MenuItem.separator(),
      MenuItem(label: localizations.exportDomainHar, onClick: (_) => exportDomainHar()),
      MenuItem.separator(),
      MenuItem(label: localizations.repeatDomainRequests, onClick: (_) => repeatDomainRequests()),
      MenuItem.separator(),
      MenuItem(label: localizations.delete, onClick: (_) => _delete()),
    ]);

    popUpContextMenu(menu);
  }

  //重复域名下请求
  void repeatDomainRequests() async {
    var list = widget.body.toList().reversed;
    for (var requestWidget in list) {
      var request = requestWidget.request.copy(uri: requestWidget.request.requestUrl);
      var proxyInfo = widget.proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", widget.proxyServer.port) : null;
      try {
        await HttpClients.proxyRequest(request, proxyInfo: proxyInfo, timeout: const Duration(seconds: 3));
        if (mounted) FlutterToastr.show(localizations.reSendRequest, rootNavigator: true, context);
      } catch (e) {
        if (mounted) FlutterToastr.show('${localizations.fail}$e', rootNavigator: true, context);
      }
    }
  }

  void exportDomainHar() {
    widget.onExportHar?.call(widget.domain);
  }

  Menu hostFilterMenu() {
    return Menu(items: [
      MenuItem(
          label: localizations.domainBlacklist,
          onClick: (_) {
            HostFilter.blacklist.add(Uri.parse(widget.domain).host);
            configuration.flushConfig();
            FlutterToastr.show(localizations.addSuccess, context);
          }),
      MenuItem(
          label: localizations.domainWhitelist,
          onClick: (_) {
            HostFilter.whitelist.add(Uri.parse(widget.domain).host);
            configuration.flushConfig();
            FlutterToastr.show(localizations.addSuccess, context);
          }),
      MenuItem(
          label: localizations.deleteWhitelist,
          onClick: (_) {
            HostFilter.whitelist.remove(Uri.parse(widget.domain).host);
            configuration.flushConfig();
            FlutterToastr.show(localizations.deleteSuccess, context);
          }),
    ]);
  }

  void _delete() {
    widget.onDelete?.call(widget.domain);
    widget.requestMap.clear();
    widget.body.clear();
    FlutterToastr.show(localizations.deleteSuccess, context);
  }
}

class HostWidget extends StatelessWidget {
  final String host;
  final Function()? onMenu;

  const HostWidget(this.host, {super.key, this.onMenu});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onSecondaryTap: onMenu,
        child: ListTile(
            minLeadingWidth: 25,
            leading: const Icon(Icons.arrow_right, size: 18),
            dense: true,
            horizontalTitleGap: 0,
            contentPadding: const EdgeInsets.only(left: 3, right: 8),
            visualDensity: const VisualDensity(vertical: -3.6),
            title: Text(host,
                textAlign: TextAlign.left,
                style: const TextStyle(fontSize: 14),
                maxLines: 1,
                overflow: TextOverflow.ellipsis)));
  }
}
