import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_styled_toast/flutter_styled_toast.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:prime_chat/basic/Api.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;


import 'Entities.dart';
import 'Global.dart';

class Handler {
  Handler(this.name) : value = prefs.getString(name) ?? "";
  final String name;
  late String value;
  Future<bool> set(String value) async {
    this.value = value;
    return prefs.setString(name, value);
  }
}

final method = Method._();

class Method {
  Method._();

  Future init() async {
    _token = Handler("token");
    _name = Handler("name");
    _password = Handler("password");
    _uid = Handler("uid");
    return await preLogin();
  }

  late Handler _token;
  late Handler _name;
  late Handler _password;
  late Handler _uid;
  get dio => token.isEmpty
      ? Dio()
      : Dio(BaseOptions(
          headers: {HttpHeaders.authorizationHeader: "Bearer $token"}));

  get token => _token.value;
  set token(value) => _token.set(value);
  get name => _name.value;
  set name(value) => _name.set(value);
  get password => _password.value;
  set password(value) => _password.set(value);
  get uid => _uid.value;
  set uid(value) => _uid.set(value);
  get user => types.User(id: uid);

  Future _handleDioError(DioError e, {int retry = 0}) async {
    final res = e.response;
    final req = e.requestOptions;
    final data = jsonDecode(res.toString());
    final code = res == null ? -1 : res.statusCode;
    debugPrint("api error, path=${req.path} code=$code error=${data['error']}");
    switch (code) {
      case HttpStatus.unauthorized:
        if (await login()) {
          final res = await request(req.path,
              data: req.data,
              queryParameters: req.queryParameters,
              options: Options(method: req.method),
              retry: retry);
          return res;
        }
        break;
    }
  }

  Future<Response?> request(String path,
      {data,
      Map<String, dynamic>? queryParameters,
      Options? options,
      int retry = 0}) async {
    if (retry < 0) return null;
    try {
      final res = await dio.request(
        path,
        data: data,
        queryParameters: queryParameters,
        options: options,
      );
      return res;
    } on DioError catch (e) {
      final ret = await _handleDioError(e, retry: retry);
      if (ret is Response) {
        return ret;
      }
    }
    return null;
  }

  Future<bool> get authorized async {
    final res = await request("$server/home", options: Options(method: "get"));
    if (res?.statusCode == HttpStatus.ok) {
      debugPrint("token=$token authorized");
      final data = jsonDecode(res.toString());
      debugPrint("authorized: ${data.toString()}");
      uid = data["uid"].toString();
      return true;
    }
    return false;
  }

  Future<bool> login() async {
    final name = _name.value, password = _password.value;
    debugPrint('login: name=$name password=$password');
    if (name.isEmpty || password.isEmpty) return false;

    final res = await request("$server/token",
        data: {"name": name, "password": password},
        options: Options(method: "post"));
    if (res?.statusCode == HttpStatus.ok) {
      final data = jsonDecode(res.toString());
      token = data['token'];
      debugPrint('login ok, token=$token');
      // Todo: 修改uid的更新方式为：login时获取profile
      return authorized;
    }
    return false;
  }

  logout() {
    token = "";
    password = "";
  }

  Future<bool> preLogin() async {
    if (await authorized) return true;
    return login();
  }

  Future<List<Profile>?> getFriends() async {
    final res =
        await request('$server/friends', options: Options(method: "get"));
    if (res?.statusCode == HttpStatus.ok) {
      var data = jsonDecode(res.toString());
      List<Profile> friends = [];
      data["friends"].forEach((v) => friends.add(Profile.fromJson(v)));
      // debugPrint(friends.toString());
      return friends;
    }
    return null;
  }

  Future<Profile?> getProfile({String? name}) async {
    name = name ?? _name.value;
    debugPrint("get profile of $name");
    final res =
        await request("$server/profile/$name", options: Options(method: "get"));
    if (res?.statusCode == HttpStatus.ok) {
      final data = jsonDecode(res.toString());
      final profile = Profile.fromJson(data["profile"]);
      debugPrint("profile get, name=${profile.name}");
      return profile;
    }
    return null;
  }

  Future download(
    String url,
    String savePath, {
    Map<String, dynamic>? queryParams,
    CancelToken? cancelToken,
    dynamic data,
    Options? options,
    void Function(int, int)? onReceiveProgress,
  }) async {
    try {
      return await dio.download(
        url,
        savePath,
        queryParameters: queryParams,
        cancelToken: cancelToken,
        onReceiveProgress: onReceiveProgress,
      );
    } on DioError catch (e) {
      print(e);
      if (CancelToken.isCancel(e)) {
        EasyLoading.showInfo('下载已取消！');
      } else {
        if (e.response != null) {
          // _handleErrorResponse(e.response);
        } else {
          EasyLoading.showError(e.message);
        }
      }
    } on Exception catch (e) {
      // EasyLoading.showError(e.toString());
    }
  }

  Future<String?> downloadImage(String path, String fileServer) async {
    Directory dir = await getApplicationDocumentsDirectory();
    String dirPath = dir.path;
    final savePath = "$dirPath/$path";
    File file = File(savePath);
    debugPrint("savePath=$savePath");
    if (file.existsSync()) return savePath;

    try {
      final res = await dio.download('$fileServer/$path', savePath);
      debugPrint(res.toString());
    } on Exception catch (e) {
      debugPrint("文件下载失败");
      return null;
    }
    return savePath;
  }

  Future<bool> updateAvatar(Uint8List buff, String suf) async {
    final file = MultipartFile.fromBytes(buff, filename: 'avatar$suf');
    final res =
        await request("$server/avatar", data: FormData.fromMap({"file": file}));
    return res?.statusCode == HttpStatus.ok;
  }

  Future<List<types.Message>?> getMessages(String roomId) async {
    final res = await request("$server/messages/$roomId", options: Options(method: "get"));
    if (res?.statusCode != HttpStatus.ok) return null;
    final data = jsonDecode(res.toString());
    List<types.Message> messages = [];
    debugPrint(data.toString());
    for (var msg in data["messages"]) {
      messages.add(types.TextMessage(
        author: types.User(id: msg["authorId"].toString()),
        id: msg["id"],
        text: msg["content"],
        createdAt: msg["createdAt"]*1000 ?? 0
      ));
    }
    messages.sort((a, b) => b.createdAt!.compareTo(a.createdAt!));
    debugPrint(messages.toString());
    return messages;
  }

  Future<bool> sendMessage(
      String roomId, types.MessageType type, String content,
      {int receiverId = 0}) async {
    final res = await request("$server/message", data: {
      "roomId": roomId,
      "type": type.name,
      "content": content,
      "receiverId": receiverId
    }, options: Options(method: "post"));
    return res?.statusCode == HttpStatus.ok;
  }

  String directRoomId(String id) {
    String a = uid, b = id;
    final res = a.compareTo(b);
    switch (res) {
      case 0: throw Exception("id equals to current user's id");
      case 1: return "direct:$b,$a";
      case -1: return "direct:$a,$b";
    }
    return "";
  }
}
