import logging
from typing import Tuple, Optional

import safe_radix32

from clarabot_nano.common.handler.base import NanoHandlerError
from clarabot_nano.common.protocol.errors import (
    NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_TEMPORARY_FAILURE,
)
from clarabot_nano.common.protocol.request import NanoRequestContext
from clarabot_nano.common.protocol.response import NanoBaseResponse
from clarabot_nano.common.request.server_general import NanoSearchRequest
from clarabot_nano.common.solr.rest import nano_query_get, preparse_solr_response, extract_query_response
from clarabot_nano.server.base.config import ServerConfig
from clarabot_nano.server.base.handler.base import AbstractNanoServerHandler
from clarabot_nano.server.base.handler.filesystem.base import AbstractFileSystemHandler
from clarabot_nano.server.general.handler.base import GeneralServerHandleHelper
from clarabot_nano.utility.path.validate import ValidationError

logger = logging.getLogger(__name__)


class NanoSearchResponse(NanoBaseResponse):
    DOCS = "docs"


class GetSearchHandler(AbstractFileSystemHandler):
    REJECTS_ANONYMOUS_IDENTITY = False  # Further authorization is by room membership and group flags.

    HANDLER_KEY = NanoSearchRequest.TYPE_KEY

    helper: GeneralServerHandleHelper  # fix type hinting

    def __init__(self, helper: GeneralServerHandleHelper, extracted_config: ServerConfig, context: dict, payload: dict):
        AbstractFileSystemHandler.__init__(self, helper, extracted_config, context, payload)
        if not self.extracted_config.solr_query_url:
            raise NanoHandlerError(
                NANO_REQUEST_ERROR_HANDLER_TEMPORARY_FAILURE, "Solr is not available for search", self.context
            )
        self.query = self.payload.get(NanoSearchRequest.QUERY)
        if not isinstance(self.query, str) or len(self.query) == 0:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid query", self.context)
        self.language = self.payload.get(NanoSearchRequest.LANGUAGE)
        if not isinstance(self.language, str) and self.language is not None:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Invalid language", self.context)
        try:
            self.root_path = self.chroot.path("").as_posix()  # use forward slashes uniformly
        except (OSError, ValidationError):  # pragma: no cover; handler base would already raise for this
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Path root is invalid", self.context)

    async def _create_request_key(self) -> Tuple[bool, Optional[list], int]:
        # caching should be investigated... client side search history will kind of cache
        return False, None, 1

    async def _execute_initial(self) -> dict:
        d = await AbstractNanoServerHandler._execute_initial(self)
        async with nano_query_get(
            self.helper.session,
            self.extracted_config.solr_query_url,
            self.extracted_config.solr_auth_header,
            self.helper.create_search_query_args(self.root_path, self.query, self.language),
        ) as r:
            result, success = await preparse_solr_response(r)
            if not success:
                raise NanoHandlerError(
                    NANO_REQUEST_ERROR_HANDLER_TEMPORARY_FAILURE, "Search query failed", self.context
                )
        result = extract_query_response(result)
        highlighting = result.get("highlighting")
        # create result docs
        room_id = safe_radix32.encode_fw(self.context[NanoRequestContext.ROOM][NanoRequestContext.ROOM_ID])
        docs = []
        for solr_doc in result["docs"]:
            for accessor in solr_doc["accessors"]:
                root, _, path = accessor.rpartition("//")  # right-partition to skip UNC root on Windows
                if root == self.root_path and path:
                    break
            else:  # pragma: no cover; this normally never happens; can't even think how to make it happen
                logger.error(
                    "Solr returned document with inconsistent accessors: %s %s", self.root_path, solr_doc["accessors"]
                )
                continue
            doc = {
                "rid": room_id,
                "path": path,  # from accessors
                "size": solr_doc.get("size"),
                "mtime": round(mtime / 1000000000, 6) if (mtime := solr_doc.get("mtime")) else None,
                "score": solr_doc.get("score"),
            }
            # add highlight snippets if any
            if highlighting:
                h_snips = highlighting.get(solr_doc["path"])
                if h_snips:  # assuming ONLY one text_* field has any content so use first not empty key
                    for snippets in h_snips.values():
                        if snippets:
                            doc["snippets"] = snippets
                            break
            docs.append(doc)
        d[NanoSearchResponse.DOCS] = docs
        return d

    async def _execute_part(self, part: int, s_hmac_start_part: Optional[int]) -> dict:
        raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST, "Part request is invalid", self.context)

    def _log_message(self) -> str:
        return f"Search in {self.root_path} for {self.query}"
