# Copyright (C) 2018-2019  The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information

"""Memory Elastic Search backend

"""

import datetime  # noqa serialization purposes
import hashlib
import logging

from ast import literal_eval
from typing import Optional

import psycopg2  # noqa serialization purposes


logger = logging.getLogger(__name__)


class BasicSerializer:
    """For memory elastic search implementation (not for production)

    """

    def __init__(self, *args, **kwargs):
        pass

    def dumps(self, *args, **kwargs):
        return str(*args)


class BasicTransport:
    """For memory elastic search implementation, (not for production)

    """

    def __init__(self, *args, **kwargs):
        self.serializer = BasicSerializer()


class MemoryElasticsearch:
    """Memory Elasticsearch instance (for test purposes)

    Partial implementation oriented towards index storage (and not search)

    For now, its sole client is the scheduler for task archival purposes.

    """

    def __init__(self, *args, **kwargs):
        self.index = {}
        self.mapping = {}
        self.settings = {}
        self.indices = self  # HACK
        self.main_mapping_key: Optional[str] = None
        self.main_settings_key: Optional[str] = None
        self.transport = BasicTransport()

    def create(self, index, **kwargs):
        logger.debug(f"create index {index}")
        logger.debug(f"indices: {self.index}")
        logger.debug(f"mapping: {self.mapping}")
        logger.debug(f"settings: {self.settings}")
        self.index[index] = {
            "status": "opened",
            "data": {},
            "mapping": self.get_mapping(self.main_mapping_key),
            "settings": self.get_settings(self.main_settings_key),
        }
        logger.debug(f"index {index} created")

    def close(self, index, **kwargs):
        """Close index"""
        idx = self.index.get(index)
        if idx:
            idx["status"] = "closed"

    def open(self, index, **kwargs):
        """Open index"""
        idx = self.index.get(index)
        if idx:
            idx["status"] = "opened"

    def bulk(self, body, **kwargs):
        """Bulk insert document in index"""
        assert isinstance(body, str)
        all_data = body.split("\n")
        if all_data[-1] == "":
            all_data = all_data[:-1]  # drop the empty line if any
        ids = []
        # data is sent as tuple (index, data-to-index)
        for i in range(0, len(all_data), 2):
            # The first entry is about the index to use
            # not about a data to index
            # find the index
            index_data = literal_eval(all_data[i])
            idx_name = index_data["index"]["_index"]
            # associated data to index
            data = all_data[i + 1]
            _id = hashlib.sha1(data.encode("utf-8")).hexdigest()
            parsed_data = eval(data)  # for datetime
            self.index[idx_name]["data"][_id] = parsed_data
            ids.append(_id)

        # everything is indexed fine
        return {"items": [{"index": {"status": 200, "_id": _id,}} for _id in ids]}

    def mget(self, *args, body, index, **kwargs):
        """Bulk indexed documents retrieval"""
        idx = self.index[index]
        docs = []
        idx_docs = idx["data"]
        for _id in body["ids"]:
            doc = idx_docs.get(_id)
            if doc:
                d = {
                    "found": True,
                    "_source": doc,
                }
                docs.append(d)
        return {"docs": docs}

    def stats(self, index, **kwargs):
        idx = self.index[index]  # will raise if it does not exist
        if not idx or idx["status"] == "closed":
            raise ValueError("Closed index")  # simulate issue if index closed

    def exists(self, index, **kwargs):
        return self.index.get(index) is not None

    def put_mapping(self, index, body, **kwargs):
        self.mapping[index] = body
        self.main_mapping_key = index

    def get_mapping(self, index, **kwargs):
        return self.mapping.get(index) or self.index.get(index, {}).get("mapping", {})

    def put_settings(self, index, body, **kwargs):
        self.settings[index] = body
        self.main_settings_key = index

    def get_settings(self, index, **kwargs):
        return self.settings.get(index) or self.index.get(index, {}).get("settings", {})
