import os
from pathlib import Path
from typing import List, Set, Union

from clarabot_nano.common.i18n import DETECTED_SYSTEM_LANGUAGE
from clarabot_nano.common.solr.rest import quote_escape
from clarabot_nano.resource import get_resource_path


def get_index_supported_languages() -> Set[str]:
    languages = set()
    for item in get_resource_path("template", "solr", "configsets", "nano", "lang").iterdir():
        if not item.name.islower() or not item.name.isalpha() or not item.is_dir():
            continue
        if not (item / "snippet.xml").is_file():
            continue
        languages.add(item.name)
    return languages


def get_effective_languages_by_config(
    supported_languages: Set[str], indexed_languages: Union[List[str], str, None]
) -> List[str]:
    if indexed_languages == "all":
        effective_config_languages = sorted(supported_languages)
    elif isinstance(indexed_languages, list) and indexed_languages:
        # select supported languages from an explicit list
        selected_languages = set(lang.lower() for lang in indexed_languages)
        selected_languages.intersection_update(supported_languages)
        effective_config_languages = sorted(selected_languages)
    else:  # empty list or "false" value
        effective_config_languages = {"en"}
        if DETECTED_SYSTEM_LANGUAGE in supported_languages:
            effective_config_languages.add(DETECTED_SYSTEM_LANGUAGE)
        effective_config_languages = sorted(effective_config_languages)
    return effective_config_languages


def get_compiled_core_config(index_languages: List[str]) -> dict:
    """
    Create the new schema and configuration for the core.
    """
    nano_template_dir = get_resource_path("template", "solr", "configsets", "nano")
    schema = (nano_template_dir / "conf" / "managed-schema").read_text(encoding="utf-8")
    pre_ins_point, tip, post_ins_point = schema.rpartition("<!-- TEMPLATE INSERTION POINT -->")
    if not tip:
        raise Exception("Nano index schema base is missing the insertion point for dynamic languages")
    snippets = []
    for lang in index_languages:
        snippets.append((nano_template_dir / "lang" / lang / "snippet.xml").read_text(encoding="utf-8"))
    schema = "".join([pre_ins_point, tip, *snippets, post_ins_point])
    files = {
        "conf/managed-schema": schema,
        "conf/solrconfig.xml": (nano_template_dir / "conf" / "solrconfig.xml").read_text(encoding="utf-8"),
    }
    for lang in index_languages:
        for parent_path, _, file_names in os.walk(nano_template_dir / "lang" / lang):
            for file_name in file_names:
                if file_name == "snippet.xml":
                    continue
                file_path = Path(parent_path) / file_name
                files[file_path.relative_to(nano_template_dir).as_posix()] = file_path.read_text(encoding="utf-8")
    return files


def create_accessor_path(root: str, *rel_parts: str) -> str:
    """
    Create an accessor path under a root.
    Root is a resolved posix path to a directory. If it is a true root it will end with a slash.
    The relative path may be given in multiple parts. Empty values will not be used and all must be valid posix
    relative paths.
    Returns the combined path split with a double-slash. (or root ending with single-slash if rel is empty)
    NOTE: Interoperability limitation!  TODO: optimize and reinforce accessor tokenization
    Posix and NT style roots must not be mixed in the same database. The root designation of double-slash sequence
    can unintentionally match any UNC path for the posix root! This does not impose a problem on Windows as the
    posix root is not a canonically valid root, thus will never be used.
    The limitation can be resolved by a custom path-hierarchy tokenizer for the solr schema. It would always tokenize
    the whole root and never split it up. For indexing this would be more optimal as well because it avoids the
    unnecessary tokens.
    """
    if rel := "/".join(r for r in rel_parts if r):
        # add the relative parts after a double-slash
        if root.endswith("/"):
            return f"{root}/{rel}"
        return f"{root}//{rel}"
    if root.endswith("/"):
        return root  # real roots will match as-is
    return f"{root}/"


def create_docs_query_children(parent_accessor_path: str):
    """
    Query for direct children of the path.
    The accessor_path parameter should be supplied by the functions above.
    """
    return f'parents:"{quote_escape(parent_accessor_path)}"'


def create_docs_query_exact(parent_accessor_path: str, name: str):
    """
    Query for a specific document. NOTE: The query can return multiple matches that are not normally desired.
    Additional filtering is required to find the true exact document, example:
        /tmp/a (real dir)
        /tmp/a/xy (real file)
        /tmp/as (symlink to /tmp/a)
        /tmp/xys (symlink to /tmp/a/xy)

        Query result for ("/tmp", "as") are two docs:
            1. {"path": "/tmp/a", "accessors": ["/tmp//a", "/tmp//as"]}
                - parents:
                    "/tmp" from "/tmp//a" and "/tmp//as"
            2. {"path": "/tmp/a/xy", "accessors": ["/tmp//a/xy", "/tmp//as/xy", "/tmp//xys"]}
                - parents:
                    "/tmp" from "/tmp//xys"
                    "/tmp/a" from "/tmp//a/xy"
                    "/tmp/as" from "/tmp//as/xy"

        To filter out the true exact match the accessors must be checked for the exact value of "/tmp//as".
    """
    return (
        f'parents:"{quote_escape(parent_accessor_path)}" '
        f'AND accessors:"{quote_escape(f"{parent_accessor_path}/{name}")}"'
    )


def create_docs_query_tree(parent_accessor_path: str):
    """
    Query for all children of the path.
    """
    return f'accessors:"{quote_escape(parent_accessor_path)}"'
