from asyncio import get_event_loop
from http import HTTPStatus
from logging import getLogger
from multiprocessing import Process, Pipe
from multiprocessing.connection import Connection
from multiprocessing.queues import Queue
from typing import Callable
from uvicorn import Config, Server

from edri.api import Headers, Middleware
from edri.api.handlers import RESTHandler, HTMLHandler, WebsocketHandler
from edri.config.constant import ApiType
from edri.config.setting import HOST, PORT, API_WORKERS, API_RESPONSE_TIMEOUT, SSL_KEY, SSL_CERTIFICATE
from edri.dataclass.directive import HTTPResponseDirective
from edri.dataclass.event import Event
from edri.dataclass.response import ResponseStatus
from edri.events.api.client import Register, Unregister
from edri.utility import NormalizedDefaultDict


class Listener(Process):

    def __init__(self, api_broker_queue: Queue[Event], middlewares: list[Middleware]) -> None:
        """
        Initializes the Listener class with the specified API broker queue and list of middlewares.

        Parameters:
            api_broker_queue (Queue[Event]): The queue for receiving events from the API broker.
            middlewares (list[Middleware]): The list of middleware components to be applied to the events.

        """
        super().__init__()
        self.api_broker_queue = api_broker_queue
        self.logger = getLogger(__name__)
        self._middlewares = middlewares

    def run(self) -> None:
        if SSL_KEY and SSL_CERTIFICATE:
            config = Config(self.App(self.api_broker_queue, self._middlewares), host=HOST, port=PORT, log_level="debug",
                            workers=API_WORKERS, log_config=None, ssl_keyfile=SSL_KEY, ssl_certfile=SSL_CERTIFICATE)
        else:
            config = Config(self.App(self.api_broker_queue, self._middlewares), host=HOST, port=PORT, log_level="debug",
                            workers=API_WORKERS, log_config=None)
        server = Server(config)
        server.run()

    class App:
        def __init__(self, api_broker_queue: Queue[Event], middlewares: list[Middleware]) -> None:
            self.ab_queue = api_broker_queue
            self.logger = getLogger(__name__)
            self._middlewares = middlewares
            self.handler_wrappers: dict[str, Callable] = {
                "http": self.http,
                "websocket": self.websocket,
                "lifespan": self.lifespan
            }

        async def __call__(self, scope, receive, send) -> None:
            try:
                handler_wrapper = self.handler_wrappers.get(scope['type'])
            except KeyError as e:
                self.logger.error("Unknown type: %s", scope['type'], exc_info=e)
            else:
                headers = self.parse_headers(scope.get("headers", {}))
                await handler_wrapper(scope, receive, send, headers)

        @staticmethod
        def parse_headers(headers) -> NormalizedDefaultDict[str, Headers]:
            parsed_headers = NormalizedDefaultDict[str, Headers](list)
            for header, value in headers:
                header = header.decode(errors="replace").lower()
                if header == "cookie":
                    parsed_headers[header] = value.decode(errors="replace")
                    continue
                parsed_headers[header].extend(
                    value.decode(errors="replace").split(";"))
            return parsed_headers

        @staticmethod
        async def lifespan(_, receive, send, __) -> None:
            message = await receive()
            if message["type"] == "lifespan.startup":
                await send({"type": "lifespan.startup.complete"})
            elif message["type"] == "lifespan.shutdown":
                await send({"type": "lifespan.shutdown.complete"})

        @staticmethod
        async def is_rest_request(headers: NormalizedDefaultDict[str, Headers]) -> bool:
            # noinspection SpellCheckingInspection
            if "xmlhttprequest" in map(lambda x: x.lower(), headers.get("x-requested-with", [])):
                return True
            elif "application/json" in headers.get("accept", []):
                return True
            else:
                return False

        async def http(self, scope, receive, send, headers):
            # noinspection SpellCheckingInspection
            if await self.is_rest_request(headers):
                handler = RESTHandler[HTTPResponseDirective](scope, receive, send, headers)
            else:
                handler = HTMLHandler[HTTPResponseDirective](scope, receive, send, headers)
            try:
                event_constructor = handler.get_event_constructor()
            except ResourceWarning as e:
                self.logger.debug("Resource not found, trying file")
                if isinstance(handler, HTMLHandler):
                    if await handler.response_assets(scope["path"]):
                        return
                self.logger.debug("Unknown url %s", scope["path"], exc_info=e)
                return await handler.response_error(HTTPStatus.NOT_FOUND, {
                    "reasons": [{
                        "status_code": HTTPStatus.NOT_FOUND,
                        "message": "Unknown url %s" % scope["path"],
                    }]
                })
            if scope["method"] == "OPTIONS":
                headers = NormalizedDefaultDict[str, Headers](list, {
                    "Access-Control-Allow-Methods": [
                        ", ".join(x.value for x in handler.get_event_constructors().keys() if x != "url")
                    ]
                })
                return await handler.response(HTTPStatus.OK, data=b"", headers=headers)

            if not event_constructor:
                self.logger.debug("There is no event for method %s url %s", scope["method"], scope["path"])
                return await handler.response_error(HTTPStatus.METHOD_NOT_ALLOWED, {
                    "reasons": [{
                        "status_code": HTTPStatus.METHOD_NOT_ALLOWED,
                        "message": "There is no event for method %s url %s" % (scope["method"], scope["path"]),
                    }]
                })
            try:
                await handler.parse_body()
            except Exception as e:
                self.logger.error("Parse of body failed", exc_info=e)
                return await handler.response_error(HTTPStatus.BAD_REQUEST, {
                    "reasons": [{
                        "status_code": HTTPStatus.BAD_REQUEST,
                        "message": "Parse of body failed",
                    }]
                })
            # Create Event
            try:
                event = handler.create_event(event_constructor)
            except ValueError as e:
                self.logger.warning("Cannot create %s", event_constructor, exc_info=e)
                return await handler.response_error(HTTPStatus.BAD_REQUEST, {
                    "reasons": [{
                        "status_code": HTTPStatus.BAD_REQUEST,
                        "message": "Cannot create %s" % event_constructor,
                    }]
                })
            #
            if (register := self.register(handler.api_type())) is None:
                self.logger.error("Registration failed")
                return await handler.response_error(HTTPStatus.INTERNAL_SERVER_ERROR, {
                    "reasons": [{
                        "status_code": HTTPStatus.INTERNAL_SERVER_ERROR,
                        "message": "Registration failed",
                    }]
                })
            pipe, key = register
            event._key = key

            event_loop = get_event_loop()
            await event_loop.run_in_executor(None, self.ab_queue.put, event)
            available_data = await event_loop.run_in_executor(None, pipe.poll, API_RESPONSE_TIMEOUT)

            if not available_data:
                self.logger.error("No response was returned in %ss", API_RESPONSE_TIMEOUT)
                await handler.response_error(HTTPStatus.INTERNAL_SERVER_ERROR, {
                    "reasons": [{
                        "status_code": HTTPStatus.INTERNAL_SERVER_ERROR,
                        "message": "No response was returned in %ss" % API_RESPONSE_TIMEOUT,
                    }]
                })
                return self.unregister(pipe, key)

            event_response: Event = pipe.recv()
            if event_response.get_response() is None:
                self.logger.warning("No Response was found in the event: %s", event_response)
                await handler.response_error(HTTPStatus.INTERNAL_SERVER_ERROR, {
                    "reasons": [{
                        "status_code": HTTPStatus.INTERNAL_SERVER_ERROR,
                        "message": "No Response was found in the event: %s" % event_response,
                    }]
                })
                return self.unregister(pipe, key)

            status, headers = handler.handle_directives(event_response.get_response().get_directives())
            if status.is_success:
                if event_response.get_response().is_file_only:
                    await handler.response_file_prepare(event_response, headers=headers)
                else:
                    await handler.response(status, event_response, headers=headers)
            else:
                await handler.response_error(status, event_response, headers=headers)
            self.unregister(pipe, key)

        async def websocket(self, scope, receive, send, headers) -> None:
            handler = WebsocketHandler(scope, receive, send, headers)
            if not await handler.accept_client():
                await handler.response_error("Missing command")
                await send({'type': 'websocket.close'})
                return
            while True:
                await handler.clear()
                if not await handler.parse_body():
                    return
                try:
                    event_constructor = await handler.get_event_constructor()
                except ResourceWarning as e:
                    self.logger.debug("Missing command", exc_info=e)
                    await handler.response_error(None, {
                        "status": ResponseStatus.FAILED.name,
                        "reasons": [f"Command is missing in the message"]
                    })
                    continue
                if not event_constructor:
                    self.logger.debug("Unknown command: %s", handler.command)
                    await handler.response_error(None, {
                        "status": ResponseStatus.FAILED.name,
                        "reasons": [f"Command '{handler.command}' was not found"]
                    })
                    continue
                try:
                    event = handler.create_event(event_constructor)
                except ValueError as e:
                    self.logger.warning("Cannot create Event: %s", event_constructor, exc_info=e)
                    await handler.response_error(None, {
                        "status": ResponseStatus.FAILED.name,
                        "reasons": [f"Command '{handler.command}' cannot be converted to event"]
                    })
                    continue
                if (register := self.register(handler.type())) is None:
                    self.logger.error("Registration failed")
                    await handler.response_error(1011, {
                        "status": ResponseStatus.FAILED.name,
                        "reasons": [f"Server error during event registration"]
                    })
                    return
                pipe, key = register
                event._key = key

                event_loop = get_event_loop()
                await event_loop.run_in_executor(None, self.ab_queue.put, event)
                available_data = await event_loop.run_in_executor(None, pipe.poll, API_RESPONSE_TIMEOUT)

                if not available_data:
                    self.logger.error("No response was returned in %ss", API_RESPONSE_TIMEOUT)
                    await handler.response_error(1011, "No response was returned in %ss" % API_RESPONSE_TIMEOUT)
                    return self.unregister(pipe, key)

                event_response = pipe.recv()
                if event_response.get_response() is None:
                    self.logger.warning("No Response was found in the event: %s", event_response)
                    await handler.response_error(1011, "No Response was found in the event: %s" % event_response)
                    return self.unregister(pipe, key)
                await handler.response(None, event_response, headers=NormalizedDefaultDict[str, Headers](list))

        def register(self, api_type: ApiType) -> tuple[Connection, str] | None:
            """
            Attempts to register a new client with the API. This involves sending a
            registration request through the queue and waiting for a response.

            Returns:
                Tuple[Connection, str] | None: A tuple containing the client connection
                and a registration key if registration is successful; None otherwise.
            """
            client_pipe, client_ab_pipe = Pipe()
            self.ab_queue.put(Register(socket=client_ab_pipe, type=api_type))
            if not client_pipe.poll(timeout=10):
                self.logger.critical("Client registration timeout!")
                client_pipe.close()
                return None
            event = client_pipe.recv()
            if not isinstance(event, Register):
                self.logger.critical("Client registration failed!")
                client_pipe.close()
                return None
            # noinspection PyProtectedMember
            if not event._key:
                self.logger.critical("Key is missing, client registration failed!")
                client_pipe.close()
                return None
            client_ab_pipe.close()
            # noinspection PyProtectedMember
            self.logger.debug("Client was registered %s", event._key)
            # noinspection PyProtectedMember
            return client_pipe, event._key

        def unregister(self, client_pipe: Connection, key: str) -> None:
            """
            Unregisters a client based on the provided key and closes their communication pipe.

            Parameters:
                client_pipe (Connection): The communication pipe for the client.
                key (str): The registration key of the client to be unregistered.
            """
            unregister = Unregister()
            unregister._key = key
            self.ab_queue.put(unregister)
            client_pipe.close()
            self.logger.debug("Client has left - %s", key)
