import logging
from io import StringIO
from typing import Tuple, Optional

import pybase64
import safe_radix32
from clarabot_nano_common.common.error import log_escape_str
from clarabot_nano_common.common.handler.error import NanoHandlerError
from clarabot_nano_common.common.protocol.errors import (
    NANO_REQUEST_ERROR_HANDLER_BAD_REQUEST,
    NANO_REQUEST_ERROR_HANDLER_TEMPORARY_FAILURE,
)
from clarabot_nano_common.common.protocol.request import NanoRequestContext
from clarabot_nano_common.common.protocol.response import NanoBaseResponse
from clarabot_nano_common.common.solr.rest import nano_query_get, preparse_solr_response, extract_query_response
from clarabot_nano_common.server.base.handler.base import AbstractNanoServerHandler
from clarabot_nano_common.server.general.config import GeneralServerConfig
from clarabot_nano_common.server.general.handler.base import GeneralServerHandleHelper, AbstractGeneralFileSystemHandler

from clarabot_nano.common.request.server_general import NanoSearchRequest

logger = logging.getLogger(__name__)


class NanoSearchResponse(NanoBaseResponse):
    DOCS = "docs"


class GetSearchHandler(AbstractGeneralFileSystemHandler):
    REJECTS_ANONYMOUS_IDENTITY = False  # Further authorization is by room membership and group flags.
    EXECUTION_STATE_CAN_CONFLICT = False  # cacheable and request_key are constant, no conflict is possible

    HANDLER_KEY = NanoSearchRequest.TYPE_KEY

    def __init__(
        self, helper: GeneralServerHandleHelper, extracted_config: GeneralServerConfig, context: dict, payload: dict
    ):
        AbstractGeneralFileSystemHandler.__init__(self, helper, extracted_config, context, payload)
        if not self.extracted_config.solr_query_url:
            raise NanoHandlerError(NANO_REQUEST_ERROR_HANDLER_TEMPORARY_FAILURE, "Solr not ready", self)
        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, "Missing query", self)
        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)
        self.root_path = self.chroot.root.as_posix()  # use forward slashes uniformly

    async def _create_request_key(self, secure: bool = False) -> Tuple[bool, Optional[list], int]:
        # caching should be investigated... client side search history will kind of cache
        return self._secure_request_key(False, None, 1) if secure else 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, "Query failed", self)
        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 = []
        # NOTE: If the drive-root is a true FS root, it will naturally end with a slash. Since we r-partition on
        # the double-slash marker in the accessor paths to recover the drive-path of the item, the natural slash is
        # also removed from the value to be tested. If we do not correct for it, such drive roots will never match
        # any documents for their accessors. See note "Filtering/2." in the docstring of create_accessor_path().
        accessor_path_stripped_root = self.root_path.rstrip("/")
        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 == accessor_path_stripped_root 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"),
            }
            if img_data := solr_doc.get("img_data"):
                doc["img0"] = pybase64.b64decode(img_data)
            # 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)

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

    def build_error_message(self, buf: StringIO) -> None:
        buf.write("Search for ")
        buf.write(log_escape_str(self.payload.get(NanoSearchRequest.QUERY)))
        buf.write(" in ")
        buf.write(log_escape_str(self.get_drive_root_for_error_message()))
