# -*- coding: utf-8 -*-
import logging

from django.db import connections, transaction, connection
from events.surveyme.logic.database import clean_field_name


logger = logging.getLogger(__name__)


class ProxyToQuerySetMixin(object):
    # todo: test me
    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError(name)
        else:
            return getattr(self.get_queryset(), name)


class BulkCreateOrUpdateMixin(object):  # todo: test me
    """
        Выполняет запрос вида
        INSERT INTO my_table (my_table.a, my_table.b)
        VALUES ('key1','value'), ('key1','value2')
        ON CONFLICT (id) DO UPDATE SET my_table.b = EXCLUDED.b)
    """

    def bulk_create_or_update(self, field_names, values):
        """
            Args:
                field_names (List[str]): Имена полей для поиска/обновления строк в базе.
                values (List[List[str]]): Список соответствущих значений поля из `field_names` каждой строки.
        """
        q = '"' if connection.vendor == 'postgresql' else '`'
        base_sql = 'INSERT INTO {q}{table}{q} ({fields}) VALUES'.format(
            table=self.model._meta.db_table,
            fields=','.join('{q}{0}{q}'.format(field_name, q=q) for field_name in field_names),
            q=q,
        )

        values_sql_template = []
        update_values = []
        for value in values:
            update_values += value
            values_sql_template.append('({0})'.format(','.join(['%s' for i in value])))

        update_fields = []
        for model_field_name in field_names:
            update_fields.append('{q}{field}{q}=VALUES({q}{field}{q})'.format(field=model_field_name, q=q))

        if connection.vendor == 'sqlite':
            raise NotImplementedError()
        else:  # postgresql
            on_conflict_fields = [
                '{q}{field}{q} = EXCLUDED.{q}{excluded}{q}'.format(
                    field=field,
                    excluded=clean_field_name(field, self.model._meta.db_table),
                    q=q,
                )
                for field in field_names
            ]
            raw_sql = '{base_sql} {values} ON CONFLICT ({unique_field}) DO UPDATE SET {on_conflict_fields}'.format(
                base_sql=base_sql,
                values=', '.join(values_sql_template),
                on_conflict_fields=', '.join(on_conflict_fields),
                unique_field=self.get_unique_field(),
            )
        with transaction.atomic():
            cursor = connections['default'].cursor()
            cursor.execute(raw_sql, update_values)

    def get_unique_field(self):
        try:
            return self.model.MetaSource.unique_field
        except AttributeError:
            return 'id'


class SyncWithSourceMixin(BulkCreateOrUpdateMixin):
    sync_resource_name = None
    sync_client = None
    sync_page_limit = 5000
    sync_get_fields = None
    sync_result_field = 'result'

    def sync_with_source(self):
        logger.info('[%s] Started sync with source.', self.sync_resource_name)
        if not self.sync_resource_name:
            raise ValueError('Необходимо указать sync_resource_name для менеджера модели.')
        page = 0
        has_next = True
        while has_next:
            page += 1
            response_data = self.get_remote_items_for_sync(page)
            response_data_result = response_data.get(self.sync_result_field)
            if response_data_result:
                self.process_response(response_data_result)
                logger.info('[%s] Processed page %s (len: %s).',
                            self.sync_resource_name,
                            page,
                            len(response_data_result),
                            )
            has_next = self.get_next_page_link(response_data)

    def get_next_page_link(self, response_data):
        return response_data.get('links', {}).get('next')

    def get_remote_items_for_sync(self, page):
        response = self.sync_client.make_request(
            self.sync_resource_name,
            page=page,
            per_page=self.sync_page_limit,
            fields=self.sync_get_fields,
        )
        response.raise_for_status()
        return response.json()

    def process_response(self, remote_objects):
        values_for_create_or_update = []
        for obj_data in remote_objects:
            obj_values = []
            for source_field_name, model_field_name in self.model.MetaSource.source_to_model_fields.items():
                obj_values.append(self._deep_get_remote_object_value(obj_data, source_field_name))
            values_for_create_or_update.append(obj_values)
        self.bulk_create_or_update(self.model.MetaSource.source_to_model_fields.values(), values_for_create_or_update)

    def _deep_get_remote_object_value(self, remote_obj_data, source_field_name):
        fields = source_field_name.split('.')
        value = remote_obj_data
        for field in fields:
            value = value.get(field, {})
        # из некоторых ручек приходят не все поля объявленные в модели
        # в таком случае значение value будет - {} и сломается sql-запрос
        # для таких случаев делаем value=None и в базу улетает null
        return value if value != {} else None
