"""
Higher-level utilities to interact with Solr.
"""
from typing import List, Optional
from urllib.parse import urlencode

import aiohttp

from clarabot_nano.common.solr.rest import (
    create_nano_schema_url,
    preparse_solr_response,
    check_solr_error,
    create_nano_config_url,
)


class SolrApiError(Exception):
    pass


class SolrApiAuthError(SolrApiError):
    pass


async def get_solr_core_schema(
    session: aiohttp.ClientSession, port: int, auth_header: str, core_name: str, timeout=5.0
) -> dict:
    """
    Query the schema of the managed core.
    """
    async with session.get(
        create_nano_schema_url(port, core_name), headers={"Authorization": auth_header}, timeout=timeout
    ) as r:
        result, success = await preparse_solr_response(r)
        if not success or not isinstance(schema := result.get("schema"), dict):
            if r.status in (401, 403):
                raise SolrApiAuthError()
            raise SolrApiError(f"Failed to query solr schema\n{result}")
    return schema


async def get_solr_core_config(
    session: aiohttp.ClientSession, port: int, auth_header: str, core_name: str, timeout=5.0
) -> dict:
    """
    Query the config of the managed core.
    """
    async with session.get(
        create_nano_config_url(port, core_name), headers={"Authorization": auth_header}, timeout=timeout
    ) as r:
        result, success = await preparse_solr_response(r)
        if not success or not isinstance(config := result.get("config"), dict):
            if r.status in (401, 403):
                raise SolrApiAuthError()
            raise SolrApiError(f"Failed to query solr core config\n{result}")
    return config


async def is_solr_core_available(
    session: aiohttp.ClientSession, port: int, auth_header: str, core_name: Optional[str] = None, timeout=5.0
) -> bool:
    """
    Check if the managed core is available at all.
    If no core-name is given for explicit check, then determine if any core is available.
    """
    query_params = {"action": "STATUS", "indexInfo": "false"}
    if core_name:
        query_params["core"] = core_name
    async with session.get(
        f"http://127.0.0.1:{port}/solr/admin/cores?{urlencode(query_params)}",
        headers={"Authorization": auth_header},
        timeout=timeout,
    ) as r:
        result, success = await preparse_solr_response(r)
        if not success or not isinstance(status := result.get("status"), dict):
            if r.status in (401, 403):
                raise SolrApiAuthError()
            raise SolrApiError(f"Failed to query solr core status\n{result}")
    if isinstance(status, dict):
        if not core_name:
            return bool(status)
        if isinstance(core := status.get(core_name), dict) and core.get("name") == core_name:
            return True
    return False


async def create_solr_core(
    session: aiohttp.ClientSession, port: int, auth_header: str, core_name: str, configset_name: str, timeout=5.0
):
    """
    Create the managed core from a configset.
    """
    query_params = urlencode({"action": "CREATE", "name": core_name, "configSet": configset_name})
    async with session.get(
        f"http://127.0.0.1:{port}/solr/admin/cores?{query_params}",
        headers={"Authorization": auth_header},
        timeout=timeout,
    ) as r:
        result, success = await preparse_solr_response(r)
        if not success:
            if r.status in (401, 403):
                raise SolrApiAuthError()
            raise SolrApiError(f"Failed to create solr core\n{result}")


async def list_solr_cores(session: aiohttp.ClientSession, port: int, auth_header: str, timeout=5.0) -> List[str]:
    """
    List existing solr cores.
    """
    query_params = urlencode({"action": "STATUS", "indexInfo": "false"})
    async with session.get(
        f"http://127.0.0.1:{port}/solr/admin/cores?{query_params}",
        headers={"Authorization": auth_header},
        timeout=timeout,
    ) as r:
        result, success = await preparse_solr_response(r)
        if not success or not isinstance(status := result.get("status"), dict):
            if r.status in (401, 403):
                raise SolrApiAuthError()
            raise SolrApiError(f"Failed to list solr cores\n{result}")
    return list(status.keys())


async def unload_solr_core(
    session: aiohttp.ClientSession, port: int, auth_header: str, core_name: str, delete: bool = True, timeout=5.0
):
    """
    Unload a core from Solr if it exists.
    """
    query_params = {"action": "UNLOAD", "core": core_name}
    if delete:
        query_params["deleteIndex"] = "true"
        query_params["deleteDataDir"] = "true"
        query_params["deleteInstanceDir"] = "true"
    query_params = urlencode(query_params)
    async with session.get(
        f"http://127.0.0.1:{port}/solr/admin/cores?{query_params}",
        headers={"Authorization": auth_header},
        timeout=timeout,
    ) as r:
        result, success = await preparse_solr_response(r)
        if not success:
            if r.status == 400 and check_solr_error(result, code=400, message="Cannot unload non-existent core "):
                return  # already unloaded
            if r.status in (401, 403):
                raise SolrApiAuthError()
            raise SolrApiError(f"Failed to unload solr core\n{result}")
