# This file was auto-generated by Fern from our API Definition.

import typing
from json.decoder import JSONDecodeError

from ..commons.errors.access_denied_error import AccessDeniedError
from ..commons.errors.error import Error
from ..commons.errors.method_not_allowed_error import MethodNotAllowedError
from ..commons.errors.not_found_error import NotFoundError
from ..commons.errors.unauthorized_error import UnauthorizedError
from ..core.api_error import ApiError
from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.http_response import AsyncHttpResponse, HttpResponse
from ..core.pydantic_utilities import parse_obj_as
from ..core.request_options import RequestOptions
from .types.metrics_v2response import MetricsV2Response


class RawMetricsClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._client_wrapper = client_wrapper

    def metrics(
        self, *, query: str, request_options: typing.Optional[RequestOptions] = None
    ) -> HttpResponse[MetricsV2Response]:
        """
        Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance.

        ## V2 Differences
        - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported)
        - Direct access to tags and release fields on observations
        - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view
        - High cardinality dimensions are not supported and will return a 400 error (see below)

        For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api).

        ## Available Views

        ### observations
        Query observation-level data (spans, generations, events).

        **Dimensions:**
        - `environment` - Deployment environment (e.g., production, staging)
        - `type` - Type of observation (SPAN, GENERATION, EVENT)
        - `name` - Name of the observation
        - `level` - Logging level of the observation
        - `version` - Version of the observation
        - `tags` - User-defined tags
        - `release` - Release version
        - `traceName` - Name of the parent trace (backwards-compatible)
        - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release)
        - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version)
        - `providedModelName` - Name of the model used
        - `promptName` - Name of the prompt used
        - `promptVersion` - Version of the prompt used
        - `startTimeMonth` - Month of start_time in YYYY-MM format

        **Measures:**
        - `count` - Total number of observations
        - `latency` - Observation latency (milliseconds)
        - `streamingLatency` - Generation latency from completion start to end (milliseconds)
        - `inputTokens` - Sum of input tokens consumed
        - `outputTokens` - Sum of output tokens produced
        - `totalTokens` - Sum of all tokens consumed
        - `outputTokensPerSecond` - Output tokens per second
        - `tokensPerSecond` - Total tokens per second
        - `inputCost` - Input cost (USD)
        - `outputCost` - Output cost (USD)
        - `totalCost` - Total cost (USD)
        - `timeToFirstToken` - Time to first token (milliseconds)
        - `countScores` - Number of scores attached to the observation

        ### scores-numeric
        Query numeric and boolean score data.

        **Dimensions:**
        - `environment` - Deployment environment
        - `name` - Name of the score (e.g., accuracy, toxicity)
        - `source` - Origin of the score (API, ANNOTATION, EVAL)
        - `dataType` - Data type (NUMERIC, BOOLEAN)
        - `configId` - Identifier of the score config
        - `timestampMonth` - Month in YYYY-MM format
        - `timestampDay` - Day in YYYY-MM-DD format
        - `value` - Numeric value of the score
        - `traceName` - Name of the parent trace
        - `tags` - Tags
        - `traceRelease` - Release version
        - `traceVersion` - Version
        - `observationName` - Name of the associated observation
        - `observationModelName` - Model name of the associated observation
        - `observationPromptName` - Prompt name of the associated observation
        - `observationPromptVersion` - Prompt version of the associated observation

        **Measures:**
        - `count` - Total number of scores
        - `value` - Score value (for aggregations)

        ### scores-categorical
        Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`.

        **Measures:**
        - `count` - Total number of scores

        ## High Cardinality Dimensions
        The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues.
        Use them in filters instead.

        **observations view:**
        - `id` - Use traceId filter to narrow down results
        - `traceId` - Use traceId filter instead
        - `userId` - Use userId filter instead
        - `sessionId` - Use sessionId filter instead
        - `parentObservationId` - Use parentObservationId filter instead

        **scores-numeric / scores-categorical views:**
        - `id` - Use specific filters to narrow down results
        - `traceId` - Use traceId filter instead
        - `userId` - Use userId filter instead
        - `sessionId` - Use sessionId filter instead
        - `observationId` - Use observationId filter instead

        ## Aggregations
        Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram`

        ## Time Granularities
        Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month`
        - `auto` bins the data into approximately 50 buckets based on the time range

        Parameters
        ----------
        query : str
            JSON string containing the query parameters with the following structure:
            ```json
            {
              "view": string,           // Required. One of "observations", "scores-numeric", "scores-categorical"
              "dimensions": [           // Optional. Default: []
                {
                  "field": string       // Field to group by (see available dimensions above)
                }
              ],
              "metrics": [              // Required. At least one metric must be provided
                {
                  "measure": string,    // What to measure (see available measures above)
                  "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram"
                }
              ],
              "filters": [              // Optional. Default: []
                {
                  "column": string,     // Column to filter on (any dimension field)
                  "operator": string,   // Operator based on type:
                                        // - datetime: ">", "<", ">=", "<="
                                        // - string: "=", "contains", "does not contain", "starts with", "ends with"
                                        // - stringOptions: "any of", "none of"
                                        // - arrayOptions: "any of", "none of", "all of"
                                        // - number: "=", ">", "<", ">=", "<="
                                        // - stringObject/numberObject: same as string/number with required "key"
                                        // - boolean: "=", "<>"
                                        // - null: "is null", "is not null"
                  "value": any,         // Value to compare against
                  "type": string,       // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null"
                  "key": string         // Required only for stringObject/numberObject types (e.g., metadata filtering)
                }
              ],
              "timeDimension": {        // Optional. Default: null. If provided, results will be grouped by time
                "granularity": string   // One of "auto", "minute", "hour", "day", "week", "month"
              },
              "fromTimestamp": string,  // Required. ISO datetime string for start of time range
              "toTimestamp": string,    // Required. ISO datetime string for end of time range (must be after fromTimestamp)
              "orderBy": [              // Optional. Default: null
                {
                  "field": string,      // Field to order by (dimension or metric alias)
                  "direction": string   // "asc" or "desc"
                }
              ],
              "config": {               // Optional. Query-specific configuration
                "bins": number,         // Optional. Number of bins for histogram aggregation (1-100), default: 10
                "row_limit": number     // Optional. Maximum number of rows to return (1-1000), default: 100
              }
            }
            ```

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        HttpResponse[MetricsV2Response]
        """
        _response = self._client_wrapper.httpx_client.request(
            "api/public/v2/metrics",
            method="GET",
            params={
                "query": query,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    MetricsV2Response,
                    parse_obj_as(
                        type_=MetricsV2Response,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise Error(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise AccessDeniedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 405:
                raise MethodNotAllowedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(
                status_code=_response.status_code,
                headers=dict(_response.headers),
                body=_response.text,
            )
        raise ApiError(
            status_code=_response.status_code,
            headers=dict(_response.headers),
            body=_response_json,
        )


class AsyncRawMetricsClient:
    def __init__(self, *, client_wrapper: AsyncClientWrapper):
        self._client_wrapper = client_wrapper

    async def metrics(
        self, *, query: str, request_options: typing.Optional[RequestOptions] = None
    ) -> AsyncHttpResponse[MetricsV2Response]:
        """
        Get metrics from the Langfuse project using a query object. V2 endpoint with optimized performance.

        ## V2 Differences
        - Supports `observations`, `scores-numeric`, and `scores-categorical` views only (traces view not supported)
        - Direct access to tags and release fields on observations
        - Backwards-compatible: traceName, traceRelease, traceVersion dimensions are still available on observations view
        - High cardinality dimensions are not supported and will return a 400 error (see below)

        For more details, see the [Metrics API documentation](https://langfuse.com/docs/metrics/features/metrics-api).

        ## Available Views

        ### observations
        Query observation-level data (spans, generations, events).

        **Dimensions:**
        - `environment` - Deployment environment (e.g., production, staging)
        - `type` - Type of observation (SPAN, GENERATION, EVENT)
        - `name` - Name of the observation
        - `level` - Logging level of the observation
        - `version` - Version of the observation
        - `tags` - User-defined tags
        - `release` - Release version
        - `traceName` - Name of the parent trace (backwards-compatible)
        - `traceRelease` - Release version of the parent trace (backwards-compatible, maps to release)
        - `traceVersion` - Version of the parent trace (backwards-compatible, maps to version)
        - `providedModelName` - Name of the model used
        - `promptName` - Name of the prompt used
        - `promptVersion` - Version of the prompt used
        - `startTimeMonth` - Month of start_time in YYYY-MM format

        **Measures:**
        - `count` - Total number of observations
        - `latency` - Observation latency (milliseconds)
        - `streamingLatency` - Generation latency from completion start to end (milliseconds)
        - `inputTokens` - Sum of input tokens consumed
        - `outputTokens` - Sum of output tokens produced
        - `totalTokens` - Sum of all tokens consumed
        - `outputTokensPerSecond` - Output tokens per second
        - `tokensPerSecond` - Total tokens per second
        - `inputCost` - Input cost (USD)
        - `outputCost` - Output cost (USD)
        - `totalCost` - Total cost (USD)
        - `timeToFirstToken` - Time to first token (milliseconds)
        - `countScores` - Number of scores attached to the observation

        ### scores-numeric
        Query numeric and boolean score data.

        **Dimensions:**
        - `environment` - Deployment environment
        - `name` - Name of the score (e.g., accuracy, toxicity)
        - `source` - Origin of the score (API, ANNOTATION, EVAL)
        - `dataType` - Data type (NUMERIC, BOOLEAN)
        - `configId` - Identifier of the score config
        - `timestampMonth` - Month in YYYY-MM format
        - `timestampDay` - Day in YYYY-MM-DD format
        - `value` - Numeric value of the score
        - `traceName` - Name of the parent trace
        - `tags` - Tags
        - `traceRelease` - Release version
        - `traceVersion` - Version
        - `observationName` - Name of the associated observation
        - `observationModelName` - Model name of the associated observation
        - `observationPromptName` - Prompt name of the associated observation
        - `observationPromptVersion` - Prompt version of the associated observation

        **Measures:**
        - `count` - Total number of scores
        - `value` - Score value (for aggregations)

        ### scores-categorical
        Query categorical score data. Same dimensions as scores-numeric except uses `stringValue` instead of `value`.

        **Measures:**
        - `count` - Total number of scores

        ## High Cardinality Dimensions
        The following dimensions cannot be used as grouping dimensions in v2 metrics API as they can cause performance issues.
        Use them in filters instead.

        **observations view:**
        - `id` - Use traceId filter to narrow down results
        - `traceId` - Use traceId filter instead
        - `userId` - Use userId filter instead
        - `sessionId` - Use sessionId filter instead
        - `parentObservationId` - Use parentObservationId filter instead

        **scores-numeric / scores-categorical views:**
        - `id` - Use specific filters to narrow down results
        - `traceId` - Use traceId filter instead
        - `userId` - Use userId filter instead
        - `sessionId` - Use sessionId filter instead
        - `observationId` - Use observationId filter instead

        ## Aggregations
        Available aggregation functions: `sum`, `avg`, `count`, `max`, `min`, `p50`, `p75`, `p90`, `p95`, `p99`, `histogram`

        ## Time Granularities
        Available granularities for timeDimension: `auto`, `minute`, `hour`, `day`, `week`, `month`
        - `auto` bins the data into approximately 50 buckets based on the time range

        Parameters
        ----------
        query : str
            JSON string containing the query parameters with the following structure:
            ```json
            {
              "view": string,           // Required. One of "observations", "scores-numeric", "scores-categorical"
              "dimensions": [           // Optional. Default: []
                {
                  "field": string       // Field to group by (see available dimensions above)
                }
              ],
              "metrics": [              // Required. At least one metric must be provided
                {
                  "measure": string,    // What to measure (see available measures above)
                  "aggregation": string // How to aggregate: "sum", "avg", "count", "max", "min", "p50", "p75", "p90", "p95", "p99", "histogram"
                }
              ],
              "filters": [              // Optional. Default: []
                {
                  "column": string,     // Column to filter on (any dimension field)
                  "operator": string,   // Operator based on type:
                                        // - datetime: ">", "<", ">=", "<="
                                        // - string: "=", "contains", "does not contain", "starts with", "ends with"
                                        // - stringOptions: "any of", "none of"
                                        // - arrayOptions: "any of", "none of", "all of"
                                        // - number: "=", ">", "<", ">=", "<="
                                        // - stringObject/numberObject: same as string/number with required "key"
                                        // - boolean: "=", "<>"
                                        // - null: "is null", "is not null"
                  "value": any,         // Value to compare against
                  "type": string,       // Data type: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null"
                  "key": string         // Required only for stringObject/numberObject types (e.g., metadata filtering)
                }
              ],
              "timeDimension": {        // Optional. Default: null. If provided, results will be grouped by time
                "granularity": string   // One of "auto", "minute", "hour", "day", "week", "month"
              },
              "fromTimestamp": string,  // Required. ISO datetime string for start of time range
              "toTimestamp": string,    // Required. ISO datetime string for end of time range (must be after fromTimestamp)
              "orderBy": [              // Optional. Default: null
                {
                  "field": string,      // Field to order by (dimension or metric alias)
                  "direction": string   // "asc" or "desc"
                }
              ],
              "config": {               // Optional. Query-specific configuration
                "bins": number,         // Optional. Number of bins for histogram aggregation (1-100), default: 10
                "row_limit": number     // Optional. Maximum number of rows to return (1-1000), default: 100
              }
            }
            ```

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        AsyncHttpResponse[MetricsV2Response]
        """
        _response = await self._client_wrapper.httpx_client.request(
            "api/public/v2/metrics",
            method="GET",
            params={
                "query": query,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    MetricsV2Response,
                    parse_obj_as(
                        type_=MetricsV2Response,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 400:
                raise Error(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 401:
                raise UnauthorizedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 403:
                raise AccessDeniedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 405:
                raise MethodNotAllowedError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            if _response.status_code == 404:
                raise NotFoundError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        typing.Any,
                        parse_obj_as(
                            type_=typing.Any,  # type: ignore
                            object_=_response.json(),
                        ),
                    ),
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(
                status_code=_response.status_code,
                headers=dict(_response.headers),
                body=_response.text,
            )
        raise ApiError(
            status_code=_response.status_code,
            headers=dict(_response.headers),
            body=_response_json,
        )
