import cgi
import json
import os
import socket
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, HTTPServer

from corekit.hikframe.dl.utils.env import print_envs
from corekit.hikframe.dl.utils.filesys import search_files
from corekit.hikframe.dl.utils.server import DataFrom


class OnlineServer(BaseHTTPRequestHandler):
    """
    在线服务类
    """

    def get_file(self, save_dir, fields="file"):
        """
        获取文件参数
        """
        form = cgi.FieldStorage(fp=self.rfile, headers=self.headers, environ={
            'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': self.headers['Content-Type'],
        })
        filename = form[fields].filename
        data = form[fields].file.read()
        file_path = os.path.join(save_dir, filename)
        with open(file_path, 'wb') as f:
            f.write(data)
        return file_path

    def get_params(self):
        """
        获取参数
        """
        data = self.rfile.read(int(self.headers['content-length']))
        data = data.decode()
        return data

    def http_response(self, code, data):
        self.send_response(code)
        self.send_header("Content-type", "application/json")
        self.send_header("Access-Control-Allow-Origin", "*")
        self.send_header("Access-Control-Allow-Methods", "*")
        self.send_header("Access-Control-Allow-Headers",
                         "Authorization, Content-Type")
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())

    def wrap_results(self, predictions, mode):
        """
        输出前端识别的结果
        """
        results = []
        for item in predictions:
            if not item:
                continue
            if mode == "classify":
                if len(item) != 2:
                    raise Exception(
                        "分类结果数据应该为[[label1, score], [label2, score],...]形式")
                results.append({"label": item[0], "score": item[1]})
            elif mode == "detect":
                if len(item) != 6:
                    raise Exception(
                        "检测结果数据应该为[[label1, score, xmin, ymin, xmax, ymax], [label2, score, xmin, ymin, xmax, ymax],...]形式，坐标为相对图片尺寸的归一化结果[0~1]")
                bbox = [item[2], item[3], item[4], item[5]]
                results.append({
                                   "label": item[0],
                                   "score": item[1],
                                   "bbox": bbox
                               })
            else:
                raise Exception("只支持classify(分类)和detect(检测)两种模式.")
        return results

    @staticmethod
    def start(handler, ip="", port=8081):
        """
        开启服务
        """
        serverAddress = (ip, port)
        server = HTTPServer(serverAddress, handler)
        server.serve_forever()

    def handle_one_request(self):
        """Handle a single HTTP request.

        You normally don't need to override this method; see the class
        __doc__ string for information on how to handle specific HTTP
        commands such as GET and POST.

        """
        try:
            self.raw_requestline = self.rfile.readline(65537)
            if len(self.raw_requestline) > 65536:
                self.requestline = ''
                self.request_version = ''
                self.command = ''
                self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG)
                return
            if not self.raw_requestline:
                self.close_connection = True
                return
            if not self.parse_request():
                # An error code has been sent, just exit
                return

            # 添加 /doc 接口
            if self.command == "GET" and self.path == "/doc":
                self.command = "DOC"

            mname = 'do_' + self.command
            if not hasattr(self, mname):
                self.send_error(
                    HTTPStatus.NOT_IMPLEMENTED,
                    "Unsupported method (%r)" % self.command)
                return
            method = getattr(self, mname)
            method()
            self.wfile.flush()  # actually send the response if not already done.
        except socket.timeout as e:
            # a read or a write timed out.  Discard this connection
            self.log_error("Request timed out: %r", e)
            self.close_connection = True
            return

    def do_DOC(self):
        """
        api 文档
        """

        if getattr(type(self), '_doc_response', None):
            return self.http_response(200, getattr(type(self), '_doc_response'))

        method = "POST"
        content_type = None
        headers = []
        parameters = []
        response = []
        annotations = {}
        if getattr(self, 'do_POST', None):
            annotations = getattr(self, 'do_POST').__annotations__
        elif getattr(self, 'do_GET', None):
            method = "GET"
            annotations = getattr(self, 'do_GET').__annotations__

        for key, param in annotations.items():
            param_json = param.json()
            if key == "return":
                response = param.json()
                continue
            param_json["key"] = key
            parameters.append(param_json)
            if method == "POST":
                location = param_json.get("location")
                if location == DataFrom.JSON.value:
                    content_type = "application/json"
                if location == DataFrom.FORM.value or location == DataFrom.FILE.value:
                    content_type = "multipart/form-data"

        if content_type:
            headers.append({"key": "Content-Type", "value": content_type})

        res = {
            "method": method,
            "headers": headers,
            "parameters": parameters,
            "response": response
        }
        type(self)._doc_response = res
        return self.http_response(200, res)


class Serving(object):
    @classmethod
    def __init__(cls, server=None, save_dir=None, model_dir=None):
        cls.Server = server if server else OnlineServer
        # 显示传入的env参数
        envs = ['save_dir', 'model_dir']
        print_envs(envs)

        cls.save_dir = save_dir if save_dir else os.getenv("save_dir")
        if cls.save_dir is None:
            raise Exception("未指定保存目录. 请设置环境变量'save_dir'或者输入初始化参数 'save_dir'.")
        cls.model_dir = model_dir if model_dir else os.getenv("model_dir")
        if cls.model_dir is None:
            raise Exception("未指定模型路径. 设置环境变量'model_dir'或者输入初始化参数 'model_dir'")

    @classmethod
    def start(cls, request_handler=None, ip="", port=8081):
        if not request_handler or not issubclass(request_handler, cls.Server):
            raise Exception("未传入BaseHTTPRequestHandler的子类")
        cls.Server.start(request_handler, ip=ip, port=port)
        print('start serving...')
        print('ip:{}, port:{}'.format(ip, port))

    @classmethod
    def search_file(cls, path, label):
        """
        搜索指定路径下带标签的文件
        """
        results = search_files(path, label)
        return results
