"""
Module contains points for monitoring's.
"""
from abc import ABC, abstractmethod
from contextlib import contextmanager
from dataclasses import field
from time import perf_counter
from typing import Dict, Optional, Union
from vlutils.structures.dataclasses import dataclass
[docs]class BaseMonitoringPoint(ABC):
    """
    Abstract class for points
    Attributes:
        eventTime (float): event time as timestamp
    """
    # str: point series name ("errors", "request", etc.)
    series: str
    __slots__ = ("eventTime",)
    def __init__(self, eventTime: float):
        self.eventTime = eventTime
    @property
    @abstractmethod
    def tags(self) -> Dict[str, Union[int, float, str]]:
        """
        Get tags from point. We supposed that tags are indexing data
        Returns:
            dict with tags.
        """
    @property
    @abstractmethod
    def fields(self) -> Dict[str, Union[int, float, str]]:
        """
        Get tags from point. We supposed that fields are not indexing data
        Returns:
            dict with fields.
        """ 
[docs]def getRoute(resource: str, method: str) -> str:
    """
    Get a request route, concatenation of a request method and a request resource
    Args:
        resource: resource
        method: method
    Returns:
        "{method}:{resource}"
    """
    return f"{method}:{resource}" 
[docs]class BaseRequestMonitoringPoint(BaseMonitoringPoint):
    """
    Base class for point which is associated with requests.
    Attributes:
        requestId (str): request id
        route (str): concatenation of a request method and a request resource
        service (str): service name
        requestTime (float): a request processing start timestamp
        statusCode (int): status code of a request response.
    """
    __slots__ = ("requestId", "route", "service", "statusCode")
    def __init__(self, requestId: str, resource: str, method: str, requestTime: float, service: str, statusCode: int):
        super().__init__(requestTime)
        self.route = getRoute(resource, method)
        self.requestId = requestId
        self.service = service
        self.statusCode = statusCode
    @property
    def tags(self) -> Dict[str, Union[int, float, str]]:
        """
        Get tags
        Returns:
            dict with following keys: "route", "service", "status_code"
        """
        return {"route": self.route, "service": self.service, "status_code": self.statusCode}
    @property
    def fields(self) -> Dict[str, Union[int, float, str]]:
        """
        Get fields
        Returns:
            dict with following keys: "request_id"
        """
        return {"request_id": self.requestId} 
[docs]class RequestMonitoringPoint(BaseRequestMonitoringPoint):
    """
    Request monitoring point is suspended for monitoring all requests and measure a request time and etc.
    Attributes:
        executionTime (float): execution time
        additionalTags (dict): additional tags which was specified for the request
        additionalFields (dict): additional fields which was specified for the request
    """
    #: series "request"
    series = "requests"
    __slots__ = ("executionTime", "additionalTags", "additionalFields")
    def __init__(
        self,
        requestId: str,
        resource: str,
        method: str,
        executionTime: float,
        requestTime: float,
        service: str,
        statusCode: int,
        additionalTags: Optional[Dict[str, Union[str, float, int]]] = None,
        additionalFields: Optional[Dict[str, Union[str, float, int]]] = None,
    ):
        super().__init__(
            requestId=requestId,
            requestTime=requestTime,
            service=service,
            resource=resource,
            method=method,
            statusCode=statusCode,
        )
        self.executionTime = executionTime
        self.additionalTags = additionalTags if additionalTags is not None else {}
        self.additionalFields = additionalFields if additionalFields is not None else {}
    @property
    def tags(self) -> Dict[str, Union[int, float, str]]:
        """
        Get tags.
        Returns:
            dict with base tags and additional tags
        """
        baseTags = super().tags
        baseTags.update(self.additionalTags)
        return baseTags
    @property
    def fields(self) -> Dict[str, Union[int, float, str]]:
        """
        Get fields.
        Returns:
            dict with base fields, "execution_time" and additional tags
        """
        baseFields = super().fields
        baseFields["execution_time"] = self.executionTime
        baseFields.update(self.additionalFields)
        return baseFields 
[docs]class RequestErrorMonitoringPoint(BaseRequestMonitoringPoint):
    """
    Request monitoring point is suspended for monitoring requests errors (error codes)
    Attributes:
        errorCode (int): error code
        additionalTags (dict): additional tags which was specified for the request
        additionalFields (dict): additional fields which was specified for the request
    """
    __slots__ = ("additionalTags", "additionalFields", "errorCode")
    #: series "errors"
    series = "errors"
    def __init__(
        self,
        requestId: str,
        resource: str,
        method: str,
        errorCode: int,
        service: str,
        requestTime: float,
        statusCode: int,
        additionalTags: Optional[Dict[str, Union[str, float, int]]] = None,
        additionalFields: Optional[Dict[str, Union[str, float, int]]] = None,
    ):
        super().__init__(
            requestId=requestId,
            requestTime=requestTime,
            service=service,
            resource=resource,
            method=method,
            statusCode=statusCode,
        )
        self.errorCode = errorCode
        self.additionalTags = additionalTags if additionalTags is not None else {}
        self.additionalFields = additionalFields if additionalFields is not None else {}
    @property
    def tags(self) -> Dict[str, Union[int, float, str]]:
        """
        Get tags.
        Returns:
            dict with base tags, "error_code" and  additional tags
        """
        baseTags = super().tags
        baseTags["error_code"] = self.errorCode
        baseTags.update(self.additionalTags)
        return baseTags
    @property
    def fields(self) -> Dict[str, Union[int, float, str]]:
        """
        Get fields.
        Returns:
            dict with base fields and additional tags
        """
        baseFields = super().fields
        baseFields.update(self.additionalFields)
        return baseFields 
[docs]@contextmanager
def monitorTime(monitoringData: DataForMonitoring, fieldName: str):
    """
    Context manager for timing  execution time.
    Args:
        monitoringData: container for saving result
        fieldName: field name
    """
    start = perf_counter()
    yield
    monitoringData.fields[fieldName] = perf_counter() - start