# -*- coding: utf-8 -*-
from textwrap import dedent
from datacloud.dev_utils.transfer.yt_to_ydb import table_description
from datacloud.dev_utils.logging.logger import get_basic_logger
from datacloud.dev_utils.yql import yql_helpers

logger = get_basic_logger(__name__)


class YdbGeoLogsTableDescription(table_description.YdbTableDescription):
    def __init__(self, table_path, ydb_connection_params):
        super(YdbGeoLogsTableDescription, self).__init__(table_path, ydb_connection_params)
        self.schema = """
        (
            hashed_cid Uint64,
            points String,
            primary key (hashed_cid)
        )
        """
        self.fields = """
            hashed_cid,
            points
        """


class InterestingCryptaTableDescription(table_description.YdbTableDescription):
    def __init__(self, table_path, ydb_connection_params):
        super(InterestingCryptaTableDescription, self).__init__(table_path, ydb_connection_params)
        self.schema = """
        (
            hashed_id Uint64,
            hashed_cid Uint64,
            primary key (hashed_id)
        )
        """
        self.fields = """
            hashed_id,
            hashed_cid
        """


class GeoTablesListTableDescription(table_description.YdbTableDescription):
    def __init__(self, table_path, ydb_connection_params):
        super(GeoTablesListTableDescription, self).__init__(table_path, ydb_connection_params)
        self.schema = dedent("""\
        (
            date String,
            crypta String,
            log String,
            is_delete_permited Bool,
            additional String,
            primary key (date)
        )
        """)

        self.fields = """
            date,
            crypta,
            log,
            is_delete_permited,
            additional
        """

    class Record(object):
        __slots__ = ('date', 'crypta', 'log', 'is_delete_permited', 'additional')

        def __init__(self, date, crypta=None, log=None, is_delete_permited=None, additional=None):
            self.date = date
            self.crypta = crypta
            self.log = log
            self.is_delete_permited = is_delete_permited
            self.additional = additional

        def __str__(self):
            return '({}: {}, {}, {}, {})'.format(
                self.date,
                self.crypta,
                self.log,
                self.is_delete_permited,
                self.additional
            )

        def __repr__(self):
            return self.__str__()

        def __eq__(self, other):
            return self.date == other.date and self.crypta == other.crypta \
                and self.crypta_path == other.crypta_path and self.weights == other.weights

    def select(self, yql_client):
        query = self._select_request.format(ydb_table=self)
        resp = yql_client.query(query).run().get_results()
        table = list(resp)[0]

        return [self.Record(*row) for row in table.rows]

    def delete(self, yql_client, date):
        query = self._delete_request.format(ydb_table=self, date=date)
        request = yql_client.query(query).run()

        if not request.is_success:
            raise yql_helpers.YqlExecutionException((request.status, list(map(str, request.errors))))

    def insert(self, yql_client, record):
        query = self._insert_request.format(ydb_table=self, record=record)
        request = yql_client.query(query).run()

        if not request.is_success:
            raise yql_helpers.YqlExecutionException((request.status, list(map(str, request.errors))))

    _select_request = dedent("""\
        USE [{ydb_table.connection_params.database}];

        SELECT
            {ydb_table.fields}
        FROM [{ydb_table.path}]
        WHERE is_delete_permited
        ORDER BY date
    """)

    _delete_request = dedent("""\
        USE [{ydb_table.connection_params.database}];
        DELETE FROM [{ydb_table.path}]
        WHERE date == "{date}"
    """)

    _insert_request = dedent("""\
        USE [{ydb_table.connection_params.database}];
        INSERT INTO [{ydb_table.path}] (
            {ydb_table.fields}
        )
        VALUES (
            "{record.date}",
            "{record.crypta}",
            "{record.log}",
            {record.is_delete_permited},
            "{record.additional}"
        )
    """)


class GeoPathTableDescription(table_description.YdbTableDescription):
    def __init__(self, table_path, ydb_connection_params):
        super(GeoPathTableDescription, self).__init__(table_path, ydb_connection_params)
        self.schema = dedent("""\
        (
            internal_score_name String,
            crypta_path String,
            geo_path String,
            weights String,
            primary key (internal_score_name)
        )
        """)

        self.fields = """
            internal_score_name,
            crypta_path,
            geo_path,
            weights
        """

    class Record(object):
        __slots__ = ('internal_score_name', 'crypta_path', 'geo_path', 'weights')

        def __init__(self, internal_score_name, crypta_path=None, geo_path=None, weights=None):
            self.internal_score_name = internal_score_name
            self.crypta_path = crypta_path
            self.geo_path = geo_path
            self.weights = weights

        def __str__(self):
            return '({}: {}, {}, {})'.format(
                self.internal_score_name,
                self.crypta_path,
                self.geo_path,
                self.weights
            )

        def __repr__(self):
            return self.__str__()

        def __eq__(self, other):
            return self.internal_score_name == other.internal_score_name and self.geo_path == other.geo_path \
                and self.crypta_path == other.crypta_path and self.weights == other.weights

    def select(self, yql_client, crypta_path, geo_path):
        query = self._select_request.format(ydb_table=self, crypta_path=crypta_path, geo_path=geo_path)
        resp = yql_client.query(query).run().get_results()
        table = list(resp)[0]

        return [self.Record(*row) for row in table.rows]

    def delete(self, yql_client, internal_score_names):
        query = self._delete_request.format(ydb_table=self, internal_score_names=internal_score_names)
        request = yql_client.query(query).run()

        if not request.is_success:
            raise yql_helpers.YqlExecutionException((request.status, list(map(str, request.errors))))

    def insert(self, yql_client, records):
        values = ',\n'.join(self._record_value.format(record=record) for record in records)
        query = self._insert_request.format(ydb_table=self, values=values)
        request = yql_client.query(query).run()

        if not request.is_success:
            raise yql_helpers.YqlExecutionException((request.status, list(map(str, request.errors))))

    def replace(self, yql_client, records):
        values = ',\n'.join(self._record_value.format(record=record) for record in records)
        query = self._replace_request.format(ydb_table=self, values=values)
        request = yql_client.query(query).run()

        if not request.is_success:
            raise yql_helpers.YqlExecutionException((request.status, list(map(str, request.errors))))

    @staticmethod
    def replace_in_records(records, old_crypta_path, old_geo_path, new_crypta_path, new_geo_path):
        for record in records:
            if record.crypta_path == old_crypta_path:
                record.crypta_path = new_crypta_path
            if record.geo_path == old_geo_path:
                record.geo_path = new_geo_path

        return records

    def replace_paths(self, yql_client, old_crypta_path, old_geo_path, new_crypta_path, new_geo_path):
        records = self.select(yql_client, old_crypta_path, old_geo_path)
        if len(records) == 0:
            logger.warning('No records found to replace!')
            return

        new_records = self.replace_in_records(records, old_crypta_path, old_geo_path, new_crypta_path, new_geo_path)
        self.replace(yql_client, new_records)

        return new_records

    _select_request = dedent("""\
        USE [{ydb_table.connection_params.database}];

        SELECT
            {ydb_table.fields}
        FROM [{ydb_table.path}]
        WHERE crypta_path == "{crypta_path}" OR geo_path == "{geo_path}"
    """)

    _delete_request = dedent("""\
        USE [{ydb_table.connection_params.database}];
        DELETE FROM [{ydb_table.path}]
        WHERE internal_score_name in ({internal_score_names})
    """)

    _insert_request = dedent("""\
        USE [{ydb_table.connection_params.database}];
        INSERT INTO [{ydb_table.path}] (
            {ydb_table.fields}
        )
        VALUES {values}
    """)

    _replace_request = dedent("""\
        USE [{ydb_table.connection_params.database}];
        REPLACE INTO [{ydb_table.path}] (
            {ydb_table.fields}
        )
        VALUES {values}
    """)

    _record_value = dedent("""\
        (
            "{record.internal_score_name}",
            "{record.crypta_path}",
            "{record.geo_path}",
            "{record.weights}"
        )
    """)
