# coding:utf-8

import re
import typing as t
from functools import cached_property
from urllib.parse import parse_qsl

from python_multipart.multipart import parse_options_header

from makit.lib import json
from makit.web import status
from makit.web.exceptions import DisconnectError
from makit.web.file import UploadFile
from makit.web.headers import Headers
from makit.web.parser import MultiPartParser, FormParser
from makit.web.response import JsonResponse, HtmlResponse, Response, FileResponse, PlainTextResponse
from makit.web.types import Scope, Receive, Send
from makit.web.ws import WebSocket


class QueryParams(dict):
    def __init__(self, *args):
        assert len(args) < 2, "Too many arguments."
        value = args[0] if args else []
        if isinstance(value, bytes):
            value = value.decode("latin-1")
        value = parse_qsl(value, keep_blank_values=True)
        d = {item[0]: self._convert(item[1]) for item in value}
        super().__init__(**d)

    @classmethod
    def _convert(cls, value):
        if re.match(r'^\d+$', value):
            return int(value)
        elif re.match(r'\d+(\.\d+)?$', value):
            return float(value)
        elif value in ['null', 'undefined']:
            return None
        elif value.lower() == 'false':
            return False
        elif value.lower() == 'true':
            return True
        return value


class Context:

    def __init__(self, scope: Scope, receive: Receive, send: Send):
        self.app = None
        self.scope = scope
        self._receive = receive
        self._send = send
        self._body, self._json, self._form = None, None, None
        self._headers = Headers(scope=scope)
        self.response_completed = False
        self._middlewares = None  # 中间件
        self._next_middleware = None  # 下一个执行的中间件

    @cached_property
    def query(self):
        return QueryParams(self.scope["query_string"])

    @cached_property
    def params(self):
        path_params = self.scope['route'].path_params
        return path_params

    @property
    def headers(self):
        return self._headers

    @cached_property
    def method(self):
        """request method"""
        return self.scope['method']

    @property
    def route(self):
        return self.scope.get('route')

    async def body(self) -> bytes:
        if self._body is None:
            chunks = []
            async for chunk in self._stream():
                chunks.append(chunk)
            self._body = b"".join(chunks)
        return self._body

    async def json(self):
        if self._json is None:
            content_type_header = self.headers.get("Content-Type")
            if content_type_header:
                content_type, options = parse_options_header(content_type_header)
                if content_type != b'application/json':
                    return {}
            data = await self.body()
            data = data.decode('utf-8') or '{}'
            data = json.loads(data)
            self._json = data
        return self._json

    async def form(self):
        if self._form is None:
            content_type_header = self.headers.get("Content-Type")
            content_type, options = parse_options_header(content_type_header)
            if content_type == b"multipart/form-data":
                multipart_parser = MultiPartParser(self.headers, self._stream())
                self._form = await multipart_parser.parse()
            elif content_type == b"application/x-www-form-urlencoded":
                form_parser = FormParser(self.headers, self._stream())
                self._form = await form_parser.parse()
            else:
                self._form = []
        return self._form

    async def files(self):
        form_items = await self.form()
        return [item for name, item in form_items if isinstance(item, UploadFile)]

    def handle_api_output(self, output):
        """ 用于在子类中实现对API输出的统一处理 """
        return output

    async def respond(self, content, status_code=status.HTTP_200_OK, headers=None):
        if self.response_completed:
            return
        response = Response(content, status_code=status_code, headers=headers)
        await response(self.scope, self._receive, self._send)
        self.response_completed = True

    async def respond_text(self, content, status_code=status.HTTP_200_OK, headers=None):
        if self.response_completed:
            return
        response = PlainTextResponse(content, status_code=status_code, headers=headers)
        await response(self.scope, self._receive, self._send)
        self.response_completed = True

    async def respond_json(self, data, status_code=200, headers=None):
        if self.response_completed:
            return
        data = json.predumps(data)
        response = JsonResponse(data, status_code=status_code, headers=headers)
        await response(self.scope, self._receive, self._send)
        self.response_completed = True

    async def respond_html(self, content, status_code=status.HTTP_200_OK, headers=None):
        if self.response_completed:
            return
        response = HtmlResponse(content, status_code=status_code, headers=headers)
        await response(self.scope, self._receive, self._send)
        self.response_completed = True

    async def respond_file(
            self,
            file,
            filename=None,
            headers=None,
            content_disposition_type='attachment',
            stat_result=None,
            status_code=200
    ):
        if self.response_completed:
            return
        response = FileResponse(
            file,
            filename=filename,
            status_code=status_code,
            headers=headers,
            stat_result=stat_result,
            content_disposition_type=content_disposition_type
        )
        await response(self.scope, self._receive, self._send)
        self.response_completed = True

    async def unauthorized(self, message, headers=None):
        await self.respond_json(message, status_code=status.HTTP_401_UNAUTHORIZED, headers=headers)

    def websocket(self):
        ws = WebSocket(self.scope, self._receive, self._send)
        return ws

    async def next(self):
        """
        执行下一个中间件，只能在中间件中被调用
        """
        if self._middlewares is None:
            self._middlewares = []
            for middleware_class, args, kwargs in self.route.middlewares:
                middleware = middleware_class(*args, **kwargs)
                self._middlewares.insert(0, middleware)

        if len(self._middlewares) > 0:
            middleware = self._middlewares.pop()
            await middleware(self)
        else:
            await self.route(self)

    async def _stream(self) -> t.AsyncGenerator[bytes, None]:
        if self._body is not None:
            yield self._body
            yield b""
            return
        while True:
            message = await self._receive()
            if message["type"] == "http.request":
                body = message.get("body", b"")
                if body:
                    yield body
                if not message.get("more_body", False):
                    break
            elif message["type"] == "http.disconnect":
                raise DisconnectError('http disconnect')
        yield b""
