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

import aiohttp
from clarabot_nano_common.common.solr.rest import preparse_solr_response, create_nano_schema_url
from clarabot_nano_common.server.base.handler.base import AbstractNanoServerHandler
from clarabot_nano_common.server.general.cachedb import IndexCacheDatabase
from clarabot_nano_common.server.general.config import GeneralServerConfig
from clarabot_nano_common.server.general.handler.base import GeneralServerHandleHelper
from clarabot_nano_common.server.general.handler.filesystem.get import GetFileSystemHandler
from clarabot_nano_common.server.general.handler.filesystem.ls import LsFileSystemHandler
from clarabot_nano_common.server.general.handler.filesystem.peek import PeekFileSystemHandler
from clarabot_nano_common.server.general.ls_cachedb import LsCacheDatabase

from clarabot_nano.common.zmq.inproc_client import AioInprocClient
from clarabot_nano.server.base.process import ServerProcess
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.backup_interval import EditBackupIntervalHandler
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.drive_add_backup import DriveAddBackupRoomHandler
from clarabot_nano.server.general.handler.admin.config_update.drive_del_backup import DriveDeleteBackupRoomHandler
from clarabot_nano.server.general.handler.admin.config_update.edit_name import EditNameHandler
from clarabot_nano.server.general.handler.admin.config_update.replace_secrets import ReplaceSecretsHandler
from clarabot_nano.server.general.handler.admin.config_update.require_explicit_peer_trust import (
    EditRequireExplicitPeerTrustHandler,
)
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.filesystem.thumbnails import ThumbnailsFileSystemHandler
from clarabot_nano.server.general.handler.search.get import GetSearchHandler


class GeneralServerProcess(ServerProcess):
    _extracted_config: GeneralServerConfig = None  # to fix type hinting

    def _get_extracted_config_cls(self):
        return GeneralServerConfig

    async def _on_new_extracted_config(self, old_extracted_config: Optional[GeneralServerConfig]):
        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, gateway_client, handler
    ) -> AsyncContextManager[GeneralServerHandleHelper]:
        async with IndexCacheDatabase.create(
            str(extracted_config.data_dir / "cche.db"), sqlite_timeout=10.0
        ) as cache_db:
            async with LsCacheDatabase.create(
                str(extracted_config.data_dir / "lsch.db"), sqlite_timeout=10.0
            ) as ls_cache_db:
                async with aiohttp.ClientSession() as session:
                    helper = GeneralServerHandleHelper(self._pool, session, cache_db, ls_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_caches(helper)),  # TODO: only for worker 1
            asyncio.create_task(helper.cache_db.closed.wait()),
            asyncio.create_task(helper.ls_cache_db.closed.wait()),
        ]

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

    async def _cleanup_old_caches(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()
            await helper.ls_cache_db.drop_old()
            await asyncio.sleep(40.0 + 40.0 * random.random())
