import random
import time
import zlib
from multiprocessing import Process, Queue

from saas.protos.rtyserver_pb2 import TMessage

from ..services.logbroker import LogBroker
from ..services.search import Search
from ..settings import config_app, logger
from ..utils.exceptions import (
    InvalidResponseException,
    AnswerNotCompletedException,
    ErrorResponseException
)
from ..utils.messages import (
    Document,
    IntInvalidResponseCount,
    BaseInvalidResponseCount,
    IntAnswerNotCompletedCount,
    IntErrorResponseCount,
    RottenModifyTimeDocsCount,
    RottenWriteTimeDocsCount
)
from ..utils import (
    get_value_by_field_name,
    get_attr_value_by_key
)


class LogBrokerReceiver(Process):
    def __init__(self,
                 search_data_extractor_queue: Queue,
                 unistat_queue: Queue):
        super().__init__()
        self.__search_data_extractor_queue = search_data_extractor_queue
        self.__unistat_queue = unistat_queue
        self.__search_service = Search(upper_search_url=config_app['search']['upper_search_url'],
                                       int_search_url=config_app['search']['int_search_url'],
                                       upper_cgi_params=config_app['search']['upper_cgi_params'],
                                       int_cgi_params=config_app['search']['int_cgi_params'],
                                       base_cgi_params=config_app['search']['base_cgi_params'])
        self.__mercury_out_timestamp = config_app['logbroker']['mercury_out_timestamp_field'].split('.')

        logbroker_host, logbroker_port = config_app['logbroker']['endpoint'].split(':')
        self.__logbroker = LogBroker(host=logbroker_host,
                                     port=int(logbroker_port),
                                     topics=config_app['logbroker']['topics'].split(','),
                                     consumer=config_app['logbroker']['consumer'],
                                     tvm_id=config_app.getint('logbroker', 'tvm_id'),
                                     tvm_secret=config_app['logbroker']['tvm_secret'],
                                     dsts_tvm_id=config_app.getint('logbroker', 'dsts_tvm_id'),
                                     wait_timeout=config_app.getint('logbroker', 'wait_timeout'),
                                     max_time_lag_ms=config_app.getint('search', 'index_timeout'))

        index_timeout = config_app.getint('search', 'index_timeout')
        max_int_rps = config_app.getint('search', 'max_int_rps')
        allowable_error_sec = config_app.getint('search', 'allowable_error_sec')
        self.__period = index_timeout // (max_int_rps * allowable_error_sec)

    def run(self) -> None:
        weight = 0
        last_period_num = 0
        document = None

        for proto_message in self.__logbroker.read():
            try:
                current_ts = time.time()
                write_ts = proto_message.meta.write_time_ms // 1000

                if (current_ts - write_ts) > config_app.getint('logbroker', 'max_wtime_lag'):
                    self.__unistat_queue.put(RottenWriteTimeDocsCount(value=1, document=None))
                    continue

                current_weight = random.random()
                message = TMessage()
                message.ParseFromString(zlib.decompress(proto_message.data, 16 + zlib.MAX_WBITS))

                # Если событие - не обновление документа
                if message.MessageType != 1:
                    continue

                modify_ts = message.Document.ModificationTimestamp
                if (current_ts - modify_ts) > config_app.getint('logbroker', 'max_mtime_lag'):
                    self.__unistat_queue.put(RottenModifyTimeDocsCount(value=1, document=None))
                    continue

                message_timestamp = get_value_by_field_name(obj=message,
                                                            field=self.__mercury_out_timestamp)
                period_num = message_timestamp // self.__period

                if document is not None and period_num != last_period_num:
                    self.__search_data_extractor_queue.put(document)
                    weight = 0
                    document = None

                if current_weight > weight:
                    docs_from_int = None
                    try:
                        docs_from_int = list(self.__search_service.get_documents_from_int(query=f'url:{message.Document.Url}'))
                    except (InvalidResponseException, KeyError):
                        self.__unistat_queue.put(IntInvalidResponseCount(value=1, document=None))
                    except AnswerNotCompletedException:
                        self.__unistat_queue.put(IntAnswerNotCompletedCount(value=1, document=None))
                    except ErrorResponseException:
                        self.__unistat_queue.put(IntErrorResponseCount(value=1, document=None))
                    if docs_from_int:
                        int_document = docs_from_int[0]
                        doc_id = int_document['DocId'].split('-')[-1]
                        searcher_hostname = get_attr_value_by_key(data=int_document['FirstStageAttribute'],
                                                                  key='_SearcherHostname')
                        base_host, base_port = searcher_hostname.split(':')
                        searcher_hostname = f'{base_host}:{int(base_port) - 1}'

                        if searcher_hostname is not None:
                            docs_from_base = None
                            try:
                                docs_from_base = self.__search_service.request_to_base(searcher_hostname=searcher_hostname,
                                                                                       doc_id=doc_id)
                            except (InvalidResponseException, KeyError):
                                self.__unistat_queue.put(BaseInvalidResponseCount(value=1, document=None))

                            if docs_from_base:
                                doc_is_indexed = any(base_document['DDK']['Timestamp'] >= modify_ts
                                                     for base_document in docs_from_base)
                                if not doc_is_indexed:
                                    # Модификация документа
                                    weight = 1
                                    last_period_num = period_num
                                    document = Document(mercury_timestamp=message_timestamp,
                                                        modification_timestamp=modify_ts,
                                                        url=message.Document.Url,
                                                        doc_id=doc_id,
                                                        searcher_hostname=searcher_hostname)
                    else:
                        # Новый документ
                        weight = current_weight
                        last_period_num = period_num
                        document = Document(mercury_timestamp=message_timestamp,
                                            modification_timestamp=modify_ts,
                                            url=message.Document.Url,
                                            doc_id=None,
                                            searcher_hostname=None)
            except Exception as e:
                logger.critical(e, exc_info=True)
                raise e from None
