import json
import logging
import os
import random
import weakref
from contextlib import asynccontextmanager

from aiohttp import web, WSCloseCode, WSMessage, WSMsgType

logger = logging.getLogger(__name__)


class WsdTestingServerMock:
    def __init__(self, port: int):
        self.port = port
        self.wsd_version = random.choice(["23.05.5.3", "24.04.14.3"])
        self.wsd_version_hash = os.urandom(5).hex()
        self.url_host = f"https://doc.clarabot.com"
        self.url_base = f"{self.url_host}/browser/{self.wsd_version_hash}"

    async def _app_on_shutdown(self, app):
        for ws in set(app["websockets"]):
            await ws.close(code=WSCloseCode.GOING_AWAY, message="Server shutdown")

    @asynccontextmanager
    async def run(self):
        app = web.Application()
        app.add_routes(
            [
                web.get("/hosting/discovery", self._handle_get_discovery),
                web.get("/hosting/capabilities", self._handle_get_capabilities),
                # NOTE: The"url" path argument does not matter much because we're only mocking client-server
                # communication, and not server communication routing relays.
                web.get("/cool/{url}/ws", self._handle_cool_ws),
            ]
        )
        app["websockets"] = weakref.WeakSet()
        app.on_shutdown.append(self._app_on_shutdown)
        runner = web.AppRunner(app)
        await runner.setup()
        site = web.TCPSite(runner, "127.0.0.1", self.port)
        await site.start()
        try:
            yield self
        finally:
            await runner.cleanup()

    async def _handle_get_discovery(self, request: web.Request):
        # NOTE: The mocked response is very stripped!
        return web.Response(
            text=f"""<wopi-discovery>
    <net-zone name="external-http">

        <!-- Writer documents -->
        <app favIconUrl="{self.url_base}/images/x-office-document.svg" name="writer">
            <action default="true" ext="sxw" name="view" urlsrc="{self.url_base}/cool.html?"/>
            <action default="true" ext="odt" name="edit" urlsrc="{self.url_base}/cool.html?"/>
            <action default="true" ext="fodt" name="edit" urlsrc="{self.url_base}/cool.html?"/>
        </app>

        <!-- Calc documents -->
        <app favIconUrl="{self.url_base}/images/x-office-spreadsheet.svg" name="calc">
            <action default="true" ext="sxc" name="view" urlsrc="{self.url_base}/cool.html?"/>
            <action default="true" ext="ods" name="edit" urlsrc="{self.url_base}/cool.html?"/>
            <action default="true" ext="fods" name="edit" urlsrc="{self.url_base}/cool.html?"/>
        </app>

        <!-- Legacy MIME-type actions (compatibility) -->

        <app name="application/vnd.ms-excel">
            <action default="true" ext="" name="edit" urlsrc="{self.url_base}/cool.html?"/>
        </app>

        <!-- Writer documents -->
        <app name="application/vnd.sun.xml.writer">
            <action default="true" ext="" name="view" urlsrc="{self.url_base}/cool.html?"/>
        </app>
        <app name="application/vnd.oasis.opendocument.text">
            <action default="true" ext="" name="edit" urlsrc="{self.url_base}/cool.html?"/>
        </app>
        <app name="application/vnd.oasis.opendocument.text-flat-xml">
            <action default="true" ext="" name="edit" urlsrc="{self.url_base}/cool.html?"/>
        </app>

        <!-- End of legacy MIME-type actions -->

        <app name="Capabilities">
            <action ext="" name="getinfo" urlsrc="{self.url_host}/hosting/capabilities"/>
        </app>
    </net-zone>
</wopi-discovery>""",
            content_type="text/xml",
        )

    async def _handle_get_capabilities(self, request: web.Request):
        return web.Response(
            text=json.dumps(
                {
                    "convert-to": {"available": True, "endpoint": "/cool/convert-to"},
                    "hasDocumentSigningSupport": False,
                    "hasMobileSupport": True,
                    "hasProxyPrefix": False,
                    "hasSettingIframeSupport": True,
                    "hasTemplateSaveAs": False,
                    "hasTemplateSource": True,
                    "hasWASMSupport": False,
                    "hasWopiAccessCheck": True,
                    "hasZoteroSupport": False,
                    "productName": "Nano Editor",
                    "productVersion": self.wsd_version,
                    "productVersionHash": self.wsd_version_hash,
                    "serverId": "9bb3008a",
                }
            ),
            content_type="application/json",
        )

    async def _handle_cool_ws(self, request: web.Request):
        ws = web.WebSocketResponse()
        await ws.prepare(request)
        request.app["websockets"].add(ws)
        try:
            await self._run_cool_ws(ws)
        except Exception:
            logger.exception("ws-error")
            raise
        finally:
            request.app["websockets"].discard(ws)
        return ws

    async def _run_cool_ws(self, ws: web.WebSocketResponse):
        # Simply echo each message to test transport.
        async for msg in ws:
            msg: WSMessage
            if msg.type == WSMsgType.BINARY:
                # print("_WSD echo", len(msg.data), msg.data[:100])
                await ws.send_bytes(msg.data)
            elif msg.type == WSMsgType.ERROR:
                logger.error("ws connection error %s %s", msg, ws.exception())
                break
            else:
                logger.error("ws connection unexpected message %s", msg)
                break
