from datetime import datetime
from typing import Optional
import time

from infra.yasm.unistat import AggregationType, SuffixType


class Document:
    __slots__ = ('mercury_timestamp', 'modification_timestamp', 'url', 'doc_id', 'searcher_hostname', 'is_new',
                 'is_invalid_mtime', 'retry_count', 'received_at')

    def __init__(self,
                 mercury_timestamp: int,
                 modification_timestamp: int,
                 url: str,
                 doc_id: Optional[str] = None,
                 searcher_hostname: Optional[str] = None):
        self.mercury_timestamp = mercury_timestamp
        self.modification_timestamp = modification_timestamp
        self.url = url
        self.doc_id = doc_id
        self.searcher_hostname = searcher_hostname
        self.is_new = True
        self.is_invalid_mtime = False
        self.retry_count = 0
        self.received_at = datetime.fromtimestamp(time.time())

    def __repr__(self):
        """
        return f'Document(url="{self.url}", doc_id="{self.doc_id}", searcher_hostname="{self.searcher_hostname}", ' + \
               f'mercury_timestamp="{datetime.fromtimestamp(self.mercury_timestamp).isoformat()}", ' + \
               f'modification_timestamp="{datetime.fromtimestamp(self.modification_timestamp).isoformat()}", ' + \
               f'retry_count={self.retry_count})'
        """
        return f'doc_url={self.url}\tdoc_id={self.doc_id}\tsearcher_hostname={self.searcher_hostname}\t' + \
               f'mercury_timestamp={datetime.fromtimestamp(self.mercury_timestamp).isoformat()}\t' + \
               f'modification_timestamp={datetime.fromtimestamp(self.modification_timestamp).isoformat()}\t' + \
               f'retry_count={self.retry_count}\tdoc_received_at={self.received_at.isoformat()}'


def get_time_intervals():
    intervals = []
    for r in [(0, 10, 5), (10, 300, 10), (300, 500, 50), (500, 1000, 100), (1000, 12000, 1000)]:
        intervals += range(*r)
    return intervals


class Signal:
    name = None
    suffix_type = None
    aggregation_type = None
    write_to_log = False
    histo_intervals = None

    def __init__(self,
                 value,
                 document):
        self.value = value
        self.document = document
        self.created_at = datetime.fromtimestamp(time.time())

    def __repr__(self):
        """
        return f'Signal(name="{self.name}", value={self.get_value()}, document={repr(self.document)}, ' + \
               f'created_at="{self.created_at.isoformat()}")'
        """
        return f'name={self.name}\tvalue={self.get_value()}\tcreated_at={self.created_at.isoformat()}\t' + repr(self.document)

    def get_value(self):
        return self.value


class IndexTime(Signal):
    """
    Гистограмма времени индексации документа (время появления на поиске минус время выхода из Mercury)
    """

    name = 'index_time'
    suffix_type = SuffixType.Histogram
    write_to_log = True
    histo_intervals = get_time_intervals()

    def get_value(self):
        return super().get_value() // 10 * 10


class LostDocsCount(Signal):
    """
    Количество потерянных документов (которые не успели доехать до поиска за время 'index_timeout')
    """

    name = 'lost_docs_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class IndexedDocsCount(Signal):
    """
    Количество успешно проиндексированных документов
    """

    name = 'indexed_docs_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class CheckedDocsCount(Signal):
    """
    Количество проверенных документов (должно быть примерно равно max_int_rps * time, но не всегда)
    """

    name = 'checked_docs_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class CheckedNewDocsCount(Signal):
    """
    Количество новых проверенных документов (которые еще не проверялись до текущего момента)
    """

    name = 'checked_new_docs_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class NotFoundDocsByUrlCount(Signal):
    """
    Количество документов, которые перестали искаться по URL на инте после индексации базовым
    """

    name = 'not_found_docs_by_url_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum
    write_to_log = True


class NotFoundDocsByTitleWithQuotesCount(Signal):
    """
    Количество документов, для которых поиск по их title в кавычках (в верхнем метапоиске) вернул пустую выдачу
    """

    name = 'not_found_docs_by_title_with_quotes_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum
    write_to_log = True


class NotFoundDocsByTitleWithoutQuotesCount(Signal):
    """
    Количество документов, для которых поиск по их title без кавычек (в верхнем метапоиске) вернул пустую выдачу
    """

    name = 'not_found_docs_by_title_without_quotes_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum
    write_to_log = True


class UpperInvalidResponseCount(Signal):
    """
    Количество запросов, на которые верхний метапоиск ответил неизвестной ошибкой
    """

    name = 'upper_invalid_response_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class IntInvalidResponseCount(Signal):
    """
    Количество запросов, на которые инт ответил неизвестной ошибкой
    """

    name = 'int_invalid_response_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class BaseInvalidResponseCount(Signal):
    """
    Количество запросов, на которые базовый поиск ответил неизвестной ошибкой
    """

    name = 'base_invalid_response_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class IntAnswerNotCompletedCount(Signal):
    """
    Количество запросов к инту, в ответе которых поле DebugInfo.AnswerIsComplete == false
    """

    name = 'int_answer_is_not_complete_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class IntErrorResponseCount(Signal):
    """
    Количество запросов к инту, в ответе которых поле ErrorInfo.GotError == 1
    """

    name = 'int_error_response_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class RottenWriteTimeDocsCount(Signal):
    """
    Количество пропущенных документов из-за давности записи в логброкер (настраивается в параметре max_wtime_lag)
    """

    name = 'rotten_write_time_docs_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class RottenModifyTimeDocsCount(Signal):
    """
    Количество пропущенных документов из-за давности ModificationTimestamp (настраивается в параметре max_mtime_lag)
    """

    name = 'rotten_modify_time_docs_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class DocsNotContainsInResultPageCount(Signal):
    """
    Количество документов, которые после индексации не попали на первую страницу СЕРПа
    """

    name = 'docs_not_contains_in_result_page_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class ExpectedDocsCount(Signal):
    """
    Количество документов, в данный момент находящихся в очереди в ожидании появления на поиске
    Вычисляется эмпирически, поэтому может немного запаздывать
    """

    name = 'expected_docs_count'
    suffix_type = SuffixType.Absolute
    aggregation_type = AggregationType.Max


class InvalidModificationTimestampCount(Signal):
    """
    Количество документов, у которых Document.ModificationTimestamp > now()
    """

    name = 'invalid_modification_timestamp_count'
    suffix_type = SuffixType.Sum
    aggregation_type = AggregationType.Sum


class TimedeltaBetweenModificationAndCurrentTime(Signal):
    """
    Гистограмма разницы между Document.ModificationTimestamp и now()
    Значения пушатся только при положительной разнице
    """

    name = 'timedelta_between_modification_and_current_time'
    suffix_type = SuffixType.Histogram
    write_to_log = True
    histo_intervals = get_time_intervals()


class RefreshMonDocsLifetime(Signal):
    """
    Гистограмма времени жизни документа в сервисе refresh_mon от получения из логброкера до сигнала 'index_time' или 'lost_docs_count'
    """

    name = 'refresh_mon_docs_lifetime'
    suffix_type = SuffixType.Histogram
    histo_intervals = get_time_intervals()
