import asyncio
import random
import time
from contextlib import asynccontextmanager
from typing import Sequence, Type, AsyncContextManager, Optional, List

import aiohttp

from clarabot_nano.common.solr.rest import preparse_solr_response, create_nano_schema_url
from clarabot_nano.common.zmq.inproc_client import AioInprocClient
from clarabot_nano.server.base.config import ServerConfig
from clarabot_nano.server.base.handler.base import AbstractNanoServerHandler
from clarabot_nano.server.base.process import ServerProcess
from clarabot_nano.server.general.cachedb import IndexCacheDatabase
from clarabot_nano.server.general.handler.admin.config_update.admin_password import ChangeAdminPasswordHandler
from clarabot_nano.server.general.handler.admin.config_update.attach_room import AttachDriveHandler
from clarabot_nano.server.general.handler.admin.config_update.create_drive import CreateDriveHandler
from clarabot_nano.server.general.handler.admin.config_update.delete_drive import DeleteDriveHandler
from clarabot_nano.server.general.handler.admin.config_update.deny_anonymous import EditDenyAnonymousHandler
from clarabot_nano.server.general.handler.admin.config_update.detach_room import DetachDriveHandler
from clarabot_nano.server.general.handler.admin.config_update.edit_name import EditNameHandler
from clarabot_nano.server.general.handler.admin.get_info import GetInfoHandler
from clarabot_nano.server.general.handler.admin.get_ls import GetListingHandler
from clarabot_nano.server.general.handler.admin.get_remote_config import GetRemoteConfigHandler
from clarabot_nano.server.general.handler.base import GeneralServerHandleHelper
from clarabot_nano.server.general.handler.filesystem.get import GetFileSystemHandler
from clarabot_nano.server.general.handler.filesystem.peek import PeekFileSystemHandler
from clarabot_nano.server.general.handler.filesystem.thumbnails import ThumbnailsFileSystemHandler
from clarabot_nano.server.general.handler.search.get import GetSearchHandler


class GeneralServerProcess(ServerProcess):
    async def _on_new_extracted_config(self, old_extracted_config: Optional[ServerConfig]):
        ec = self._extracted_config  # TODO: check & document why this optimization is possible and correct
        if old_extracted_config is not None and ec.solr_query_url != old_extracted_config.solr_query_url:
            self.queue_main_event({"type": "new-configs"})  # restart work for solr-core change
        else:
            await ServerProcess._on_new_extracted_config(self, old_extracted_config)

    @asynccontextmanager
    async def _create_handle_helper(self, extracted_config) -> AsyncContextManager[GeneralServerHandleHelper]:
        async with IndexCacheDatabase.create(
            asyncio.get_event_loop(), str(extracted_config.data_dir / "cche.db"), sqlite_timeout=10.0
        ) as cache_db:
            async with aiohttp.ClientSession() as session:
                helper = GeneralServerHandleHelper(self._pool, session, cache_db, self._core_requester)
                if extracted_config.solr_query_url:
                    # Solr may have started up but the core is not yet created or initialised, so we may need to wait a
                    # little and retry the schema query.
                    until = time.perf_counter() + 12.0
                    while True:
                        try:
                            async with session.get(
                                create_nano_schema_url(extracted_config.solr_port, extracted_config.solr_core),
                                headers={"Authorization": extracted_config.solr_auth_header},
                                timeout=8.0,
                            ) as r:
                                result, success = await preparse_solr_response(r)
                                if not success or not isinstance(schema := result.get("schema"), dict):
                                    raise aiohttp.ClientError("Failed to query solr schema")  # may retry
                                break
                        except aiohttp.ClientError:
                            if time.perf_counter() > until:
                                raise
                            await asyncio.sleep(0.5)
                    helper.reflect_solr_schema(schema)
                try:
                    yield helper
                finally:
                    await helper.close()

    def _create_work_tasks(
        self, gateway_client: AioInprocClient, helper: GeneralServerHandleHelper
    ) -> List[asyncio.Future]:
        return [
            asyncio.create_task(self._cleanup_old_index_cached_values(helper)),
            asyncio.create_task(helper.cache_db.closed.wait()),
        ]

    def _init_get_handler_classes(self) -> Sequence[Type[AbstractNanoServerHandler]]:
        return [
            GetFileSystemHandler,
            PeekFileSystemHandler,
            ThumbnailsFileSystemHandler,
            GetSearchHandler,
            # Admin API
            GetInfoHandler,
            GetRemoteConfigHandler,
            GetListingHandler,
            ChangeAdminPasswordHandler,
            EditNameHandler,
            CreateDriveHandler,
            DeleteDriveHandler,
            AttachDriveHandler,
            DetachDriveHandler,
            EditDenyAnonymousHandler,
            # TODO: indexing settings, secret-replacement, server number
        ]

    async def _cleanup_old_index_cached_values(self, helper: GeneralServerHandleHelper):
        """
        The values cached for optimization can get stale, so they need to be dropped sometimes.
        """
        await asyncio.sleep(
            20.0 + 40.0 * random.random()
        )  # wait a little at startup to stagger the initial load generated by all the workers
        while True:
            await helper.cache_db.drop_old(int(time.time() - 86400))
            await asyncio.sleep((20.0 + 40.0 * random.random()) * 60)
