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

import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:flutter/services.dart';
import 'package:pixes/appdata.dart';
import 'package:pixes/foundation/app.dart';
import 'package:pixes/foundation/log.dart';
import 'package:pixes/utils/ext.dart';

export 'package:dio/dio.dart';

class MyLogInterceptor implements Interceptor {
  @override
  void onError(DioException err, ErrorInterceptorHandler handler) {
    Log.error("Network",
        "${err.requestOptions.method} ${err.requestOptions.path}\n$err\n${err.response?.data.toString()}");
    switch (err.type) {
      case DioExceptionType.badResponse:
        var statusCode = err.response?.statusCode;
        if (statusCode != null) {
          err = err.copyWith(
              message: "Invalid Status Code: $statusCode. "
                  "${_getStatusCodeInfo(statusCode)}");
        }
      case DioExceptionType.connectionTimeout:
        err = err.copyWith(message: "Connection Timeout");
      case DioExceptionType.receiveTimeout:
        err = err.copyWith(
            message: "Receive Timeout: "
                "This indicates that the server is too busy to respond");
      case DioExceptionType.unknown:
        if (err.toString().contains("Connection terminated during handshake")) {
          err = err.copyWith(
              message: "Connection terminated during handshake: "
                  "This may be caused by the firewall blocking the connection "
                  "or your requests are too frequent.");
        } else if (err.toString().contains("Connection reset by peer")) {
          err = err.copyWith(
              message: "Connection reset by peer: "
                  "The error is unrelated to app, please check your network.");
        }
      default:
        {}
    }
    handler.next(err);
  }

  static const errorMessages = <int, String>{
    400: "The Request is invalid.",
    401: "The Request is unauthorized.",
    403: "No permission to access the resource. Check your account or network.",
    404: "Not found.",
    429: "Too many requests. Please try again later.",
  };

  String _getStatusCodeInfo(int? statusCode) {
    if (statusCode != null && statusCode >= 500) {
      return "This is server-side error, please try again later. "
          "Do not report this issue.";
    } else {
      return errorMessages[statusCode] ?? "";
    }
  }

  @override
  void onResponse(
      Response<dynamic> response, ResponseInterceptorHandler handler) {
    var headers = response.headers.map.map((key, value) => MapEntry(
        key.toLowerCase(), value.length == 1 ? value.first : value.toString()));
    headers.remove("cookie");
    String content;
    if (response.data is List<int>) {
      content = "<Bytes>\nlength:${response.data.length}";
    } else {
      content = response.data.toString();
    }
    Log.addLog(
        (response.statusCode != null && response.statusCode! < 400)
            ? LogLevel.info
            : LogLevel.error,
        "Network",
        "Response ${response.realUri.toString()} ${response.statusCode}\n"
            "headers:\n$headers\n$content");
    handler.next(response);
  }

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    options.connectTimeout = const Duration(seconds: 15);
    options.receiveTimeout = const Duration(seconds: 15);
    options.sendTimeout = const Duration(seconds: 15);
    if (options.headers["Host"] == null && options.headers["host"] == null) {
      options.headers["host"] = options.uri.host;
    }
    Log.info("Network",
        "${options.method} ${options.uri}\n${options.headers}\n${options.data}");
    handler.next(options);
  }
}

class AppDio extends DioForNative {
  bool isInitialized = false;

  @override
  Future<Response<T>> request<T>(String path,
      {Object? data,
      Map<String, dynamic>? queryParameters,
      CancelToken? cancelToken,
      Options? options,
      ProgressCallback? onSendProgress,
      ProgressCallback? onReceiveProgress}) async{
    if (!isInitialized) {
      isInitialized = true;
      interceptors.add(MyLogInterceptor());
    }
    if(T == Map<String, dynamic>) {
      var res = await super.request<String>(path,
          data: data,
          queryParameters: queryParameters,
          cancelToken: cancelToken,
          options: options,
          onSendProgress: onSendProgress,
          onReceiveProgress: onReceiveProgress);
      if(res.data == null) {
        return Response(
          data: null,
          requestOptions: res.requestOptions,
          statusCode: res.statusCode,
          statusMessage: res.statusMessage,
          isRedirect: res.isRedirect,
          redirects: res.redirects,
          extra: res.extra,
          headers: res.headers
        );
      }
      try {
        var json = jsonDecode(res.data!);
        return Response(
            data: json,
            requestOptions: res.requestOptions,
            statusCode: res.statusCode,
            statusMessage: res.statusMessage,
            isRedirect: res.isRedirect,
            redirects: res.redirects,
            extra: res.extra,
            headers: res.headers
        );
      }
      catch(e) {
        var data = res.data!;
        if(data.length > 50) {
          data = "${data.substring(0, 50)}...";
        }
        throw "Failed to decode response: $e\n$data";
      }
    }
    return super.request<T>(path,
        data: data,
        queryParameters: queryParameters,
        cancelToken: cancelToken,
        options: options,
        onSendProgress: onSendProgress,
        onReceiveProgress: onReceiveProgress);
  }
}

void setSystemProxy() {
  HttpOverrides.global = _ProxyHttpOverrides()..findProxy(Uri());
}

class _ProxyHttpOverrides extends HttpOverrides {
  String proxy = "DIRECT";

  String findProxy(Uri uri) {
    var haveUserProxy = appdata.settings["proxy"] != null &&
        appdata.settings["proxy"].toString().removeAllBlank.isNotEmpty;
    if (!App.isLinux && !haveUserProxy) {
      var channel = const MethodChannel("pixes/proxy");
      channel.invokeMethod("getProxy").then((value) {
        if (value.toString().toLowerCase() == "no proxy") {
          proxy = "DIRECT";
        } else {
          if (proxy.contains("https")) {
            var proxies = value.split(";");
            for (String proxy in proxies) {
              proxy = proxy.removeAllBlank;
              if (proxy.startsWith('https=')) {
                value = proxy.substring(6);
              }
            }
          }
          proxy = "PROXY $value";
        }
      });
    } else {
      if (haveUserProxy) {
        proxy = "PROXY ${appdata.settings["proxy"]}";
      }
    }
    // check validation
    if (proxy.startsWith("PROXY")) {
      var uri = proxy.replaceFirst("PROXY", "").removeAllBlank;
      if (!uri.startsWith("http")) {
        uri += "http://";
      }
      if (!uri.isURL) {
        return "DIRECT";
      }
    }
    return proxy;
  }

  @override
  HttpClient createHttpClient(SecurityContext? context) {
    final client = super.createHttpClient(context);
    client.connectionTimeout = const Duration(seconds: 5);
    client.findProxy = findProxy;
    return client;
  }
}
