import pathlib
import asyncio

import logging
import time

from snek.view.threads import ThreadsView 

logging.basicConfig(level=logging.DEBUG)

from aiohttp import web
from aiohttp_session import (
    get_session as session_get,
    session_middleware,
    setup as session_setup,
)
from aiohttp_session.cookie_storage import EncryptedCookieStorage
from app.app import Application as BaseApplication

from snek.docs.app import Application as DocsApplication
from snek.mapper import get_mappers
from snek.service import get_services
from snek.system import http
from snek.system.cache import Cache
from snek.system.markdown import MarkdownExtension
from snek.system.middleware import cors_middleware
from snek.system.template import LinkifyExtension, PythonExtension,EmojiExtension
from snek.view.about import AboutHTMLView, AboutMDView
from snek.view.docs import DocsHTMLView, DocsMDView
from snek.view.index import IndexView
from snek.view.login import LoginView
from snek.view.logout import LogoutView
from snek.view.register import RegisterView
from snek.view.rpc import RPCView
from snek.view.status import StatusView
from snek.view.web import WebView
from snek.view.upload import UploadView
from snek.view.search_user import SearchUserView 
from snek.view.avatar import AvatarView
from snek.system.profiler import profiler_handler
from snek.view.terminal import TerminalView, TerminalSocketView
from snek.view.drive import DriveView
from  concurrent.futures import ThreadPoolExecutor
SESSION_KEY = b"c79a0c5fda4b424189c427d28c9f7c34"


@web.middleware
async def session_middleware(request, handler):
    setattr(request, "session", await session_get(request))
    response = await handler(request)
    return response

class Application(BaseApplication):

    def __init__(self, *args, **kwargs):
        middlewares = [
            cors_middleware,
            web.normalize_path_middleware(merge_slashes=True),
        ]
        self.template_path = pathlib.Path(__file__).parent.joinpath("templates")
        super().__init__(
            middlewares=middlewares, template_path=self.template_path, *args, **kwargs
        )
        session_setup(self, EncryptedCookieStorage(SESSION_KEY))
        self.tasks = asyncio.Queue()
        self._middlewares.append(session_middleware)
        self.jinja2_env.add_extension(MarkdownExtension)
        self.jinja2_env.add_extension(LinkifyExtension)
        self.jinja2_env.add_extension(PythonExtension)
        self.jinja2_env.add_extension(EmojiExtension)
        
        self.setup_router()
     
        self.cache = Cache(self)
        self.services = get_services(app=self)
        self.mappers = get_mappers(app=self)
        self.on_startup.append(self.prepare_database)

    async def create_task(self, task):
        await self.tasks.put(task)

    async def task_runner(self):
        while True:
            task = await self.tasks.get() 
            self.db.begin()
            try:
                task_start = time.time()
                await task
                task_end = time.time()
                print(f"Task {task} took {task_end - task_start} seconds")
                self.tasks.task_done()
            except Exception as ex:
                print(ex)
            self.db.commit()

    async def prepare_database(self,app):
        self.db.query("PRAGMA journal_mode=WAL")
        self.db.query("PRAGMA syncnorm=off")

        try:
            if not self.db["user"].has_index("username"):
                self.db["user"].create_index("username", unique=True)
            if not self.db["channel_member"].has_index(["channel_uid","user_uid"]):
                self.db["channel_member"].create_index(["channel_uid","user_uid"])
            if not self.db["channel_message"].has_index(["channel_uid","user_uid"]):
                self.db["channel_message"].create_index(["channel_uid","user_uid"])
        except:
            pass 
 
        await app.services.drive.prepare_all()
        self.loop.create_task(self.task_runner())

    def setup_router(self):
        self.router.add_get("/", IndexView)
        self.router.add_static(
            "/",
            pathlib.Path(__file__).parent.joinpath("static"),
            name="static",
            show_index=True,
        )
        self.router.add_view("/profiler.html", profiler_handler)
        self.router.add_view("/about.html", AboutHTMLView)
        self.router.add_view("/about.md", AboutMDView)
        self.router.add_view("/logout.json", LogoutView)
        self.router.add_view("/logout.html", LogoutView)
        self.router.add_view("/docs.html", DocsHTMLView)
        self.router.add_view("/docs.md", DocsMDView)
        self.router.add_view("/status.json", StatusView)
        self.router.add_view("/web.html", WebView)
        self.router.add_view("/login.html", LoginView)
        self.router.add_view("/login.json", LoginView)
        self.router.add_view("/register.html", RegisterView)
        self.router.add_view("/register.json", RegisterView)
        self.router.add_view("/drive.bin", UploadView)
        self.router.add_view("/drive.bin/{uid}.{ext}", UploadView)
        self.router.add_view("/search-user.html", SearchUserView)
        self.router.add_view("/search-user.json", SearchUserView)
        self.router.add_view("/avatar/{uid}.svg", AvatarView)
        self.router.add_get("/http-get", self.handle_http_get)
        self.router.add_get("/http-photo", self.handle_http_photo)
        self.router.add_get("/rpc.ws", RPCView)
        self.router.add_view("/channel/{channel}.html", WebView)
        self.router.add_view("/threads.html", ThreadsView)
        self.router.add_view("/terminal.ws", TerminalSocketView)
        self.router.add_view("/terminal.html", TerminalView)
        self.router.add_view("/drive.json", DriveView)
        self.router.add_view("/drive/{drive}.json", DriveView)

        self.add_subapp(
            "/docs",
            DocsApplication(path=pathlib.Path(__file__).parent.joinpath("docs")),
        )

    async def handle_test(self, request):

        return await self.render_template(
            "test.html", request, context={"name": "retoor"}
        )

    async def handle_http_get(self, request: web.Request):
        url = request.query.get("url")
        content = await http.get(url)
        return web.Response(body=content)

    async def handle_http_photo(self, request):
        url = request.query.get("url")
        path = await http.create_site_photo(url)
        return web.Response(
            body=path.read_bytes(), headers={"Content-Type": "image/png"}
        )

    # @time_cache_async(60)
    async def render_template(self, template, request, context=None):
        channels = []
        if not context:
            context = {}
        if request.session.get("uid"): 
            async for subscribed_channel in self.services.channel_member.find(user_uid=request.session.get("uid"), deleted_at=None, is_banned=False):
                item = {}
                other_user = await self.services.channel_member.get_other_dm_user(subscribed_channel["channel_uid"], request.session.get("uid"))
                parent_object = await subscribed_channel.get_channel()
                last_message =await parent_object.get_last_message()
                color = None 
                if last_message:
                    last_message_user = await last_message.get_user()
                    color = last_message_user["color"]
                item['color'] = color
                item["last_message_on"] = parent_object["last_message_on"]
                item["is_private"] = parent_object["tag"] == "dm"
                if other_user:
                    item["name"] = other_user["nick"]
                    item["uid"] = subscribed_channel["channel_uid"]
                else:
                    item["name"] = subscribed_channel["label"]
                    item["uid"] = subscribed_channel["channel_uid"]
                item['new_count'] = subscribed_channel['new_count'] 
                
                print(item)
                channels.append(item)
            
            channels.sort(key=lambda x: x['last_message_on'] or '', reverse=True)
            if not 'channels' in context:
                context['channels'] = channels
            if not 'user' in context:
                context['user'] = await self.services.user.get(uid=request.session.get("uid"))

        return await super().render_template(template, request, context)

executor = ThreadPoolExecutor(max_workers=200)

loop = asyncio.get_event_loop()
loop.set_default_executor(executor)

app = Application(db_path="sqlite:///snek.db")

async def main():
    await web._run_app(app, port=8081, host="0.0.0.0")

if __name__ == "__main__":
    asyncio.run(main())
