"""
Low-level utilities interact with Solr. Request creation and response parsing.
"""
import base64
import logging
import time
from typing import List, Tuple, Optional, Union
from urllib.parse import urlencode

import aiohttp

from clarabot_nano.utility.serialize import json_dumps, json_loads

logger = logging.getLogger(__name__)

USERNAME = "Nano"
NANO_CORE_NAME_PREFIX = "nano_core_"


def create_solr_auth_header(password: str) -> str:
    return f'Basic {base64.b64encode(f"{USERNAME}:{password}".encode("ascii")).decode("ascii")}'


def create_solr_core_url(host: str, port: int, core_name: str, endpoint: str) -> str:
    return f"http://{host}:{port}/solr/{core_name}/{endpoint}"


def create_nano_schema_url(port: int, core_name: str) -> str:
    return create_solr_core_url("127.0.0.1", port, core_name, "schema")


def create_nano_config_url(port: int, core_name: str) -> str:
    return create_solr_core_url("127.0.0.1", port, core_name, "config")


def create_nano_query_url(port: int, core_name: str) -> str:
    return create_solr_core_url("127.0.0.1", port, core_name, "query")


def quote_escape(arg: str) -> str:
    return arg.replace("\\", "\\\\").replace('"', '\\"')


def get_solr_error(raw_result: bytes) -> Optional[Tuple[Optional[int], Optional[str]]]:
    try:
        result = json_loads(raw_result)
    except ValueError:
        logger.exception("Failed to deserialize response from solr. (_error: %s)", raw_result)
        return
    if not isinstance(error := result.get("error"), dict):
        return
    return error.get("code"), error.get("msg")


def check_solr_error(raw_result: bytes, code=None, message=None) -> bool:
    """
    Get error code and message and check for them to be a specific value.
    This is a convenience function around get_solr_error().
    """
    error = get_solr_error(raw_result)
    if not error:
        return False
    if code is not None and error[0] != code:
        return False
    if message is not None and (not isinstance(error[1], str) or error[1].find(message) < 0):
        return False
    return True


async def preparse_solr_response(response: aiohttp.ClientResponse) -> Tuple[Union[bytes, dict], bool]:
    """
    Wait and parse a Solr response.
    The result tuple will be populated depending on how successfully the response is parsed:
        (raw_response or parsed_response, boolean indicating if the response is successful)
    When the result indicates error the caller code is responsible to log as appropriate.
    """
    raw_result = await response.read()
    if response.status != 200:
        return raw_result, False
    try:
        result = json_loads(raw_result)
    except ValueError:
        return raw_result, False
    try:
        status = result["responseHeader"]["status"]
    except (TypeError, KeyError):
        return result, False
    if status != 0:
        return result, False
    return result, True


def extract_query_response(result: dict, transfer_root_keys=("nextCursorMark", "highlighting")):
    """
    The result sis assumed to be valid and returned by preparse_solr_response().
    """
    response = result["response"]
    response["time"] = result["responseHeader"]["QTime"]
    for key in transfer_root_keys:
        if key in result:
            response[key] = result[key]
    return response


def build_query_dismax_args(
    query_expr,
    query_fields,
    fields=None,
    start=None,
    rows=None,
    sort=None,
    filter_queries=None,
    alt_query=None,
    cursor_mark=None,
    q_op=None,
    mm_auto_relax=False,
    highlight=False,
    extra_args=None,
):
    query_args = [
        ("defType", "edismax"),
        ("qf", query_fields),
    ]
    if query_expr:
        query_args.append(("q", query_expr))
    if cursor_mark:
        query_args.append(("cursorMark", cursor_mark))
    if isinstance(start, int) and start > 0:
        query_args.append(("start", start))
    if isinstance(rows, int):
        query_args.append(("rows", rows if rows > 4 else 5))
    if fields:
        query_args.append(("fl", fields))
    if sort:
        query_args.append(("sort", sort))
    if filter_queries:
        for filter_query in filter_queries:
            query_args.append(("fq", filter_query))
    if alt_query:
        query_args.append(("q.alt", alt_query))
    if q_op:
        query_args.append(("q.op", q_op))
    if mm_auto_relax:
        query_args.append(("mm.autoRelax", "true"))
    if highlight:
        query_args.append(("hl", "true"))
    if extra_args:
        query_args.extend(extra_args)
    return query_args


# def build_query_standard_args(
#     query_expr,
#     default_field,
#     fields=None,
#     start=None,
#     rows=None,
#     sort=None,
#     filter_queries=None,
#     cursor_mark=None,
#     q_op=None,
#     extra_args=None,
# ):
#     query_args = [
#         ("defType", "lucene"),
#         ("df", default_field),
#         ("q", query_expr),
#     ]
#     if cursor_mark:
#         query_args.append(("cursorMark", cursor_mark))
#     if isinstance(start, int) and start > 0:
#         query_args.append(("start", start))
#     if isinstance(rows, int):
#         query_args.append(("rows", rows if rows > 4 else 5))
#     if fields:
#         query_args.append(("fl", fields))
#     if sort:
#         query_args.append(("sort", sort))
#     if filter_queries:
#         for filter_query in filter_queries:
#             query_args.append(("fq", filter_query))
#     if q_op:
#         query_args.append(("q.op", q_op))
#     if extra_args:
#         query_args.extend(extra_args)
#     return query_args


def nano_query_get(s: aiohttp.ClientSession, url: str, auth_header: str, query_args):
    """
    Create the arguments for an HTTP POST request by an aiohttp.ClientSession instance.
    """
    return s.get(f"{url}?{urlencode(query_args)}", headers={"Authorization": auth_header})


def create_nano_update_url(port: int, core_name: str) -> str:
    """
    http://127.0.0.1:17630/solr/nano_core/update?_=1612281222000&commitWithin=1000&overwrite=true&wt=json
    [{"path": "/tmp/xy2", "file_size": 551}]
    """
    return create_solr_core_url("127.0.0.1", port, core_name, "update")


def nano_update_post(
    s: aiohttp.ClientSession,
    url: str,
    auth_header: str,
    commands_or_documents: Union[List[dict], dict],
    commit=False,
    soft_commit=False,
):
    """
    Create the arguments for an HTTP POST request by an aiohttp.ClientSession instance.
    """
    query_args = {
        "_": int(time.time() * 1000),
    }
    if commit:
        query_args["commit"] = "true"
    if soft_commit:
        query_args["softCommit"] = "true"
    return s.post(
        f"{url}?{urlencode(query_args)}",
        headers={"Content-Type": "application/json; charset=utf-8", "Authorization": auth_header},
        data=json_dumps(commands_or_documents),
    )
