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

import contextlib
import datetime as dt
import typing
from json.decoder import JSONDecodeError
from logging import error, warning

from ..core.api_error import ApiError
from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
from ..core.datetime_utils import serialize_datetime
from ..core.http_response import AsyncHttpResponse, HttpResponse
from ..core.http_sse._api import EventSource
from ..core.pydantic_utilities import parse_obj_as
from ..core.request_options import RequestOptions
from ..core.serialization import convert_and_respect_annotation_metadata
from ..errors.unprocessable_entity_error import UnprocessableEntityError
from ..types.http_validation_error import HttpValidationError
from ..types.log_level import LogLevel
from ..types.log_list_response import LogListResponse
from ..types.log_source import LogSource
from ..types.log_stream_event import LogStreamEvent
from ..types.otlp_log_ingestion_response import OtlpLogIngestionResponse
from ..types.otlp_resource_logs import OtlpResourceLogs

# this is used as the default value for optional parameters
OMIT = typing.cast(typing.Any, ...)


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

    def list(
        self,
        *,
        agent_name: str,
        branch_id: typing.Optional[str] = None,
        task_id: typing.Optional[str] = None,
        trace_id: typing.Optional[str] = None,
        level: typing.Optional[LogLevel] = None,
        source: typing.Optional[LogSource] = None,
        since: typing.Optional[dt.datetime] = None,
        until: typing.Optional[dt.datetime] = None,
        search: typing.Optional[str] = None,
        after_id: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        offset: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[LogListResponse]:
        """
        Query logs for an agent. Requires user authentication.

        Parameters
        ----------
        agent_name : str
            Name of the agent (format: namespace/name or agent ID)

        branch_id : typing.Optional[str]
            Filter by branch/deployment ID

        task_id : typing.Optional[str]
            Filter by task ID

        trace_id : typing.Optional[str]
            Filter by OTEL trace ID (32 lowercase hex chars)

        level : typing.Optional[LogLevel]
            Filter by log level

        source : typing.Optional[LogSource]
            Filter by source (stdout, stderr, server)

        since : typing.Optional[dt.datetime]
            Start timestamp filter (ISO 8601)

        until : typing.Optional[dt.datetime]
            End timestamp filter (ISO 8601)

        search : typing.Optional[str]
            Full-text search in message (case-insensitive)

        after_id : typing.Optional[str]
            Return logs after this log_id (cursor-based pagination)

        limit : typing.Optional[int]
            Maximum number of results

        offset : typing.Optional[int]
            Number of results to skip

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

        Returns
        -------
        HttpResponse[LogListResponse]
            Successful Response
        """
        _response = self._client_wrapper.httpx_client.request(
            "logs",
            method="GET",
            params={
                "agent_name": agent_name,
                "branch_id": branch_id,
                "task_id": task_id,
                "trace_id": trace_id,
                "level": level,
                "source": source,
                "since": serialize_datetime(since) if since is not None else None,
                "until": serialize_datetime(until) if until is not None else None,
                "search": search,
                "after_id": after_id,
                "limit": limit,
                "offset": offset,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    LogListResponse,
                    parse_obj_as(
                        type_=LogListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        HttpValidationError,
                        parse_obj_as(
                            type_=HttpValidationError,  # 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)

    def ingest_otlp(
        self,
        *,
        resource_logs: typing.Sequence[OtlpResourceLogs],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> HttpResponse[OtlpLogIngestionResponse]:
        """
        Ingest OpenTelemetry logs from an agent. Requires agent API key authentication.

        Parameters
        ----------
        resource_logs : typing.Sequence[OtlpResourceLogs]
            Array of ResourceLogs per OTLP spec

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

        Returns
        -------
        HttpResponse[OtlpLogIngestionResponse]
            Successful Response
        """
        _response = self._client_wrapper.httpx_client.request(
            "logs/otlp",
            method="POST",
            json={
                "resourceLogs": convert_and_respect_annotation_metadata(
                    object_=resource_logs, annotation=typing.Sequence[OtlpResourceLogs], direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    OtlpLogIngestionResponse,
                    parse_obj_as(
                        type_=OtlpLogIngestionResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return HttpResponse(response=_response, data=_data)
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        HttpValidationError,
                        parse_obj_as(
                            type_=HttpValidationError,  # 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)

    @contextlib.contextmanager
    def stream(
        self,
        *,
        agent_name: str,
        task_id: typing.Optional[str] = None,
        level: typing.Optional[LogLevel] = None,
        source: typing.Optional[LogSource] = None,
        branch_id: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.Iterator[HttpResponse[typing.Iterator[LogStreamEvent]]]:
        """
        Stream logs for an agent in real-time using Server-Sent Events (SSE). Emits multiple event types (connected, log, error) discriminated by 'type' field. Only streams NEW logs after connection (like tail -f). Auto-disconnects after 5 minutes for cost control.

        Parameters
        ----------
        agent_name : str
            Name of the agent (format: namespace/name or agent ID)

        task_id : typing.Optional[str]
            Filter by task ID

        level : typing.Optional[LogLevel]
            Minimum log level

        source : typing.Optional[LogSource]
            Filter by source (stdout, stderr, server)

        branch_id : typing.Optional[str]
            Filter by branch/version ID

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

        Yields
        ------
        typing.Iterator[HttpResponse[typing.Iterator[LogStreamEvent]]]
            SSE stream of log events
        """
        with self._client_wrapper.httpx_client.stream(
            "logs/stream",
            method="GET",
            params={
                "agent_name": agent_name,
                "task_id": task_id,
                "level": level,
                "source": source,
                "branch_id": branch_id,
            },
            request_options=request_options,
        ) as _response:

            def _stream() -> HttpResponse[typing.Iterator[LogStreamEvent]]:
                try:
                    if 200 <= _response.status_code < 300:

                        def _iter():
                            _event_source = EventSource(_response)
                            for _sse in _event_source.iter_sse():
                                if _sse.data == None:
                                    return
                                try:
                                    yield typing.cast(
                                        LogStreamEvent,
                                        parse_obj_as(
                                            type_=LogStreamEvent,  # type: ignore
                                            object_=_sse.json(),
                                        ),
                                    )
                                except JSONDecodeError as e:
                                    warning(f"Skipping SSE event with invalid JSON: {e}, sse: {_sse!r}")
                                except (TypeError, ValueError, KeyError, AttributeError) as e:
                                    warning(
                                        f"Skipping SSE event due to model construction error: {type(e).__name__}: {e}, sse: {_sse!r}"
                                    )
                                except Exception as e:
                                    error(
                                        f"Unexpected error processing SSE event: {type(e).__name__}: {e}, sse: {_sse!r}"
                                    )
                            return

                        return HttpResponse(response=_response, data=_iter())
                    _response.read()
                    if _response.status_code == 422:
                        raise UnprocessableEntityError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                HttpValidationError,
                                parse_obj_as(
                                    type_=HttpValidationError,  # 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)

            yield _stream()


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

    async def list(
        self,
        *,
        agent_name: str,
        branch_id: typing.Optional[str] = None,
        task_id: typing.Optional[str] = None,
        trace_id: typing.Optional[str] = None,
        level: typing.Optional[LogLevel] = None,
        source: typing.Optional[LogSource] = None,
        since: typing.Optional[dt.datetime] = None,
        until: typing.Optional[dt.datetime] = None,
        search: typing.Optional[str] = None,
        after_id: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        offset: typing.Optional[int] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[LogListResponse]:
        """
        Query logs for an agent. Requires user authentication.

        Parameters
        ----------
        agent_name : str
            Name of the agent (format: namespace/name or agent ID)

        branch_id : typing.Optional[str]
            Filter by branch/deployment ID

        task_id : typing.Optional[str]
            Filter by task ID

        trace_id : typing.Optional[str]
            Filter by OTEL trace ID (32 lowercase hex chars)

        level : typing.Optional[LogLevel]
            Filter by log level

        source : typing.Optional[LogSource]
            Filter by source (stdout, stderr, server)

        since : typing.Optional[dt.datetime]
            Start timestamp filter (ISO 8601)

        until : typing.Optional[dt.datetime]
            End timestamp filter (ISO 8601)

        search : typing.Optional[str]
            Full-text search in message (case-insensitive)

        after_id : typing.Optional[str]
            Return logs after this log_id (cursor-based pagination)

        limit : typing.Optional[int]
            Maximum number of results

        offset : typing.Optional[int]
            Number of results to skip

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

        Returns
        -------
        AsyncHttpResponse[LogListResponse]
            Successful Response
        """
        _response = await self._client_wrapper.httpx_client.request(
            "logs",
            method="GET",
            params={
                "agent_name": agent_name,
                "branch_id": branch_id,
                "task_id": task_id,
                "trace_id": trace_id,
                "level": level,
                "source": source,
                "since": serialize_datetime(since) if since is not None else None,
                "until": serialize_datetime(until) if until is not None else None,
                "search": search,
                "after_id": after_id,
                "limit": limit,
                "offset": offset,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    LogListResponse,
                    parse_obj_as(
                        type_=LogListResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        HttpValidationError,
                        parse_obj_as(
                            type_=HttpValidationError,  # 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)

    async def ingest_otlp(
        self,
        *,
        resource_logs: typing.Sequence[OtlpResourceLogs],
        request_options: typing.Optional[RequestOptions] = None,
    ) -> AsyncHttpResponse[OtlpLogIngestionResponse]:
        """
        Ingest OpenTelemetry logs from an agent. Requires agent API key authentication.

        Parameters
        ----------
        resource_logs : typing.Sequence[OtlpResourceLogs]
            Array of ResourceLogs per OTLP spec

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

        Returns
        -------
        AsyncHttpResponse[OtlpLogIngestionResponse]
            Successful Response
        """
        _response = await self._client_wrapper.httpx_client.request(
            "logs/otlp",
            method="POST",
            json={
                "resourceLogs": convert_and_respect_annotation_metadata(
                    object_=resource_logs, annotation=typing.Sequence[OtlpResourceLogs], direction="write"
                ),
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                _data = typing.cast(
                    OtlpLogIngestionResponse,
                    parse_obj_as(
                        type_=OtlpLogIngestionResponse,  # type: ignore
                        object_=_response.json(),
                    ),
                )
                return AsyncHttpResponse(response=_response, data=_data)
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    headers=dict(_response.headers),
                    body=typing.cast(
                        HttpValidationError,
                        parse_obj_as(
                            type_=HttpValidationError,  # 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)

    @contextlib.asynccontextmanager
    async def stream(
        self,
        *,
        agent_name: str,
        task_id: typing.Optional[str] = None,
        level: typing.Optional[LogLevel] = None,
        source: typing.Optional[LogSource] = None,
        branch_id: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[LogStreamEvent]]]:
        """
        Stream logs for an agent in real-time using Server-Sent Events (SSE). Emits multiple event types (connected, log, error) discriminated by 'type' field. Only streams NEW logs after connection (like tail -f). Auto-disconnects after 5 minutes for cost control.

        Parameters
        ----------
        agent_name : str
            Name of the agent (format: namespace/name or agent ID)

        task_id : typing.Optional[str]
            Filter by task ID

        level : typing.Optional[LogLevel]
            Minimum log level

        source : typing.Optional[LogSource]
            Filter by source (stdout, stderr, server)

        branch_id : typing.Optional[str]
            Filter by branch/version ID

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

        Yields
        ------
        typing.AsyncIterator[AsyncHttpResponse[typing.AsyncIterator[LogStreamEvent]]]
            SSE stream of log events
        """
        async with self._client_wrapper.httpx_client.stream(
            "logs/stream",
            method="GET",
            params={
                "agent_name": agent_name,
                "task_id": task_id,
                "level": level,
                "source": source,
                "branch_id": branch_id,
            },
            request_options=request_options,
        ) as _response:

            async def _stream() -> AsyncHttpResponse[typing.AsyncIterator[LogStreamEvent]]:
                try:
                    if 200 <= _response.status_code < 300:

                        async def _iter():
                            _event_source = EventSource(_response)
                            async for _sse in _event_source.aiter_sse():
                                if _sse.data == None:
                                    return
                                try:
                                    yield typing.cast(
                                        LogStreamEvent,
                                        parse_obj_as(
                                            type_=LogStreamEvent,  # type: ignore
                                            object_=_sse.json(),
                                        ),
                                    )
                                except JSONDecodeError as e:
                                    warning(f"Skipping SSE event with invalid JSON: {e}, sse: {_sse!r}")
                                except (TypeError, ValueError, KeyError, AttributeError) as e:
                                    warning(
                                        f"Skipping SSE event due to model construction error: {type(e).__name__}: {e}, sse: {_sse!r}"
                                    )
                                except Exception as e:
                                    error(
                                        f"Unexpected error processing SSE event: {type(e).__name__}: {e}, sse: {_sse!r}"
                                    )
                            return

                        return AsyncHttpResponse(response=_response, data=_iter())
                    await _response.aread()
                    if _response.status_code == 422:
                        raise UnprocessableEntityError(
                            headers=dict(_response.headers),
                            body=typing.cast(
                                HttpValidationError,
                                parse_obj_as(
                                    type_=HttpValidationError,  # 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)

            yield await _stream()
