"""
Access to SEARCH_SETTINGS Django conf.

The SEARCH_SETTINGS dict in the Django conf contains three
major blocks - 'connections', 'indexes' and 'settings'.

This module contains helper functions to extract information
from the settings, as well as validation of settings.

"""
from __future__ import annotations

import json
import os
from typing import Dict, List, Optional, Union

from django.apps import apps
from django.conf import settings
from django.db.models import Model
from elasticsearch import Elasticsearch

SettingType = Union[list, dict, int, str, bool]
SettingsType = Dict[str, SettingType]


def get_client(connection: str = "default") -> Elasticsearch:
    """Return configured elasticsearch client."""
    return Elasticsearch(get_connection_string(connection))


def get_settings() -> SettingsType:
    """Return settings from Django conf."""
    return settings.SEARCH_SETTINGS["settings"]


def get_setting(key, *default: Union[str, int, bool, list, dict]) -> SettingType:
    """Return specific search setting from Django conf."""
    if default:
        return get_settings().get(key, default[0])
    else:
        return get_settings()[key]


def set_setting(key: str, value: SettingType) -> None:
    """Set specific search setting in Django conf settings."""
    get_settings()[key] = value


def get_connection_string(connection: str = "default") -> str:
    """Return index settings from Django conf."""
    return settings.SEARCH_SETTINGS["connections"][connection]


def get_index_config(index: str) -> Dict[str, List[str]]:
    """Return index settings from Django conf."""
    return settings.SEARCH_SETTINGS["indexes"][index]


def get_index_names() -> List[str]:
    """Return list of the names of all configured indexes."""
    return list(settings.SEARCH_SETTINGS["indexes"].keys())


def get_index_mapping(index: str) -> dict:
    """
    Return the JSON mapping file for an index.

    Mappings are stored as JSON files in the mappings subdirectory of this
    app. They must be saved as {{index}}.json.

    Args:
        index: string, the name of the index to look for.

    """
    # app_path = apps.get_app_config('elasticsearch_django').path
    mappings_dir = get_setting("mappings_dir")
    filename = "%s.json" % index
    path = os.path.join(mappings_dir, filename)
    with open(path, "r") as f:
        return json.load(f)


def get_model_index_properties(instance: Model, index: str) -> List[str]:
    """Return the list of properties specified for a model in an index."""
    mapping = get_index_mapping(index)
    doc_type = instance._meta.model_name.lower()
    return list(mapping["mappings"][doc_type]["properties"].keys())


def get_index_models(index: str) -> List[Model]:
    """Return list of models configured for a named index."""
    models = []  # type: List[Model]
    for app_model in get_index_config(index).get("models"):
        app, model = app_model.split(".")
        models.append(apps.get_model(app, model))
    return models


def get_model_indexes(model: Model) -> List[str]:
    """
    Return list of all indexes in which a model is configured.

    A model may be configured to appear in multiple indexes. This function
    will return the names of the indexes as a list of strings. This is
    useful if you want to know which indexes need updating when a model
    is saved.

    Args:
        model: a Django model class.

    """
    indexes = []  # type: List[str]
    for index in get_index_names():
        for app_model in get_index_models(index):
            if app_model == model:
                indexes.append(index)
    return indexes


def get_document_models() -> Dict[str, Model]:
    """Return dict of index.doc_type: model."""
    mappings = {}  # type: Dict[str, Model]
    for i in get_index_names():
        for m in get_index_models(i):
            mappings[f"{i}.{m._meta.model_name}"] = m
    return mappings


def get_document_model(index: str, doc_type: str) -> Optional[Model]:
    """Return model for a given index.doc_type combination."""
    return get_document_models().get(f"{index}.{doc_type}")


def auto_sync(instance: Model) -> bool:
    """Return True if auto_sync is on for the model (instance)."""
    # this allows us to turn off sync temporarily - e.g. when doing bulk updates
    if not get_setting("auto_sync"):
        return False
    model_name = f"{instance._meta.app_label}.{instance._meta.model_name}"
    if model_name in get_setting("never_auto_sync", *[]):
        return False
    return True