# -*- coding: utf-8 -*-
import os
from datacloud.dev_utils.id_value import encoders as crypta_encoders
from datacloud.score_api.storage.scores.generic import ScoresStorage
from datacloud.score_api.storage.ydb.ydb_tables.score_tables.score_table import MetaScoreTable, ScoreTable
from datacloud.score_api.storage.ydb.ydb_tables.score_tables.crypta_table import CryptaTable


__all__ = [
    'YdbScoresStorage',
]


_encoders = {
    'id_value': crypta_encoders.encode_id_value,
    'phone': crypta_encoders.encode_phone,
    'email': crypta_encoders.encode_email,
    'yuid': crypta_encoders.encode_yuid,
}

# TODO: Add tests for meta score table


class YdbScoresStorage:
    """ Base class for accessing to scores """

    def __init__(self, ydb_manager, database, scores_root_path, score_dir_path):
        super(YdbScoresStorage, self).__init__()
        self._ydb_manager = ydb_manager
        self._database = database
        self._scores_root_path = scores_root_path
        self._score_dir_path = score_dir_path
        self._score_table = None
        self._crypta_table = None

    @property
    def score_table(self):
        if self._score_table is None:
            self._score_table = ScoreTable(self._ydb_manager, self._database,
                                           os.path.join(self._scores_root_path, self._score_dir_path, 'score'))
        return self._score_table

    @property
    def crypta_table(self):
        if self._crypta_table is None:
            self._crypta_table = CryptaTable(self._ydb_manager, self._database,
                                             os.path.join(self._scores_root_path, self._score_dir_path, 'crypta'))
        return self._crypta_table

    @staticmethod
    def _get_max_scores(rows):
        """
        Selects max values in dicts
        [{'a': 1}, {'a': 2}] -> {'a': 2}
        """
        if not rows:
            return {}
        keys = list(rows[0].keys())
        return {
            k: max(r[k] for r in rows)
            for k in keys
        }

    def _get_single_score_value(self, hashed_cid):
        return self.score_table.get_one(ScoreTable.Record(hashed_cid))

    def _get_score_for_multiple_cids(self, hashed_cids_list):
        return [rec.score for rec in self.score_table.get_multiple(hashed_cids_list)]

    def _get_single_hashed_cid(self, id_value):
        val = self.crypta_table.get_one(CryptaTable.Record(id_value))
        if val is None:
            return val
        return val.hashed_cid

    def _get_hashed_cids_for_all_id_values(self, hashed_id_values_list):
        return [rec.hashed_cid for rec in self.crypta_table.get_multiple(hashed_id_values_list)]

    def get_scores_for_hashed_id_values(self, hashed_id_values_list):
        hashed_cids = self._get_hashed_cids_for_all_id_values(hashed_id_values_list)
        if not hashed_cids:
            return []
        return self._get_score_for_multiple_cids(hashed_cids)

    @staticmethod
    def convert_id_values_to_hashed_id_values(info):
        for record in info:
            for id_type in record:
                if id_type in _encoders:
                    id_value = _encoders[id_type](record[id_type])
                    if id_value:
                        yield id_value

    def get_scores_for_id_values(self, info):
        hashed_id_values_list = [id_value for id_value in self.convert_id_values_to_hashed_id_values(info)]
        return self.get_scores_for_hashed_id_values(hashed_id_values_list)


class YdbScoresMetaStorage(ScoresStorage):
    # TODO: Add tests
    """ Base class for accessing to scores """

    def __init__(self, ydb_manager, database, scores_root_path, score_dir_path):
        super(YdbScoresMetaStorage, self).__init__()
        self._ydb_manager = ydb_manager
        self._database = database
        self._scores_root_path = scores_root_path
        self._score_dir_path = score_dir_path
        self._score_table = None

    @property
    def score_table(self):
        if self._score_table is None:
            self._score_table = MetaScoreTable(self._ydb_manager, self._database,
                                               os.path.join(self._scores_root_path, self._score_dir_path, 'score'))
        return self._score_table

    @staticmethod
    def _get_max_scores(rows):
        """
        Selects max values in dicts
        [{'a': 1}, {'a': 2}] -> {'a': 2}
        """
        if not rows:
            return {}
        keys = list(rows[0].keys())
        return {
            k: max(r[k] for r in rows)
            for k in keys
        }

    def _get_single_score_value(self, hashed_cid):
        return self.score_table.get_one(MetaScoreTable.Record(hashed_cid))

    def _get_score_for_multiple_cids(self, hashed_cids_list):
        return [rec.score for rec in self.score_table.get_multiple(hashed_cids_list)]

    def get_scores_for_hashed_id_values(self, hashed_id_values_list):
        return self._get_score_for_multiple_cids(hashed_id_values_list)

    @staticmethod
    def convert_id_values_to_hashed_id_values(info):
        for record in info:
            for id_type in record:
                if id_type in _encoders:
                    id_value = _encoders[id_type](record[id_type])
                    if id_value:
                        yield id_value

    def get_scores_for_id_values(self, info):
        hashed_id_values_list = [id_value for id_value in self.convert_id_values_to_hashed_id_values(info)]
        return self.get_scores_for_hashed_id_values(hashed_id_values_list)
