# Copyright 2026 Invoke Labs, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Directly call the Voker API.

See `voker.api.client.ApiClient` for a higher-level client that wraps this API client.
"""

from __future__ import annotations

import importlib.metadata
import os
import platform
import subprocess
import sys
from typing import Any, Optional, cast

import requests

from voker.schema.api import (
    CreateAgentPayload,
    CreateAgentVersionPayload,
    CreateEventPayload,
    CreateFingerprintPayload,
    CreatePersonPayload,
    EventProperties,
    UpdatePersonPayload,
)
from voker.schema.base import (
    Agent,
    AgentNotFoundError,
    AgentNotFoundResponse,
    AgentVersion,
    AgentVersionNotFoundError,
    AgentVersionNotFoundResponse,
    Fingerprint,
    FingerprintNotFoundError,
    FingerprintNotFoundResponse,
    Person,
    PersonNotFoundError,
    PersonNotFoundResponse,
)


class ApiClient:
    """Voker API Client.

    See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient)
    """

    def __init__(
        self,
        *,
        base_url: str = "https://evals.voker.ai",
        api_key: Optional[str] = None,
    ) -> None:
        self._base_url = base_url

        if api_key is None:
            api_key = os.getenv("VOKER_API_KEY")

        self._api_key = api_key

    @property
    def events(self) -> ApiClient_Events:
        """Access [events](https://app.voker.ai/docs/concepts/events#events) related endpoints."""
        return ApiClient_Events(self)

    @property
    def people(self) -> ApiClient_People:
        """Access [people](https://app.voker.ai/docs/concepts/people) related endpoints."""
        return ApiClient_People(self)

    @property
    def fingerprints(self) -> ApiClient_Fingerprints:
        """Access [fingerprints](https://app.voker.ai/docs/concepts/events#fingerprints) related endpoints."""
        return ApiClient_Fingerprints(self)

    @property
    def agents(self) -> ApiClient_Agents:
        """Access [agents](https://app.voker.ai/docs/concepts/agents#agents) related endpoints."""
        return ApiClient_Agents(self)

    @property
    def agent_versions(self) -> ApiClient_AgentVersions:
        """Access [agent versions](https://app.voker.ai/docs/concepts/agents#agent-versions) related endpoints."""
        return ApiClient_AgentVersions(self)

    def _request(self, method: str, path: str, **kwargs) -> requests.Response:
        url = f"{self._base_url}{path}"
        headers = kwargs.pop("headers", {})

        if self._api_key is not None:
            headers["Authorization"] = f"Bearer {self._api_key}"

        return requests.request(method, url, headers=headers, **kwargs)


class _ApiClient_BaseSubPath:

    def __init__(self, voker_client: ApiClient) -> None:
        self._voker_client = voker_client


class ApiClient_Events(_ApiClient_BaseSubPath):
    """Access the [events](https://app.voker.ai/docs/concepts/events) API endpoints.

    See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient_Events)
    """

    def create(
        self,
        *,
        event_name: str,
        properties: EventProperties,
        fingerprint_id: str,
        agent: Optional[str] = None,
        agent_version: Optional[str] = None,
        person: Optional[str] = None,
        session: Optional[str] = None,
    ) -> None:
        """Create a new [event](https://app.voker.ai/docs/concepts/events).

        See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient_Events.create)

        Parameters
        ----------
        event_name: str
        properties: EventProperties
        fingerprint_id: str
            The [fingerprint](https://app.voker.ai/docs/concepts/events#fingerprints) of the client that generated the event.
        agent: Optional[str]
            The name of the [agent](https://app.voker.ai/docs/concepts/agents#agents) associated with the event.
            If this agent does not exist, it will be created. If an agent is not supplied, a default agent will be used.
        agent_version: Optional[str]
            The name of the [agent version](https://app.voker.ai/docs/concepts/agents#agent-versions) associated with the event.
            If this agent version does not exist, it will be created. If an agent version is not supplied, a default version will be used.
        person: Optional[str]
            The unique identifier of the [person](https://app.voker.ai/docs/concepts/people) associated with the event.
            If the person does not exist, it will be created.
        session: Optional[str]
            The unique identifier of the [session](https://app.voker.ai/docs/concepts/events#event-sessions) associated with the event.
            If the session does not exist, it will be created.
        """
        payload = CreateEventPayload(
            event_name=event_name,
            fingerprint_id=fingerprint_id,
            properties=properties,
        )

        if agent is not None:
            payload["agent"] = agent

        if agent_version is not None:
            payload["agent_version"] = agent_version

        if person is not None:
            payload["person"] = person

        if session is not None:
            payload["session"] = session

        response = self._voker_client._request(
            "POST",
            "/api/v1/events",
            json=payload,
        )

        data = response.json()

        if response.status_code == 404:
            data = cast(FingerprintNotFoundResponse, data)
            raise FingerprintNotFoundError()

        response.raise_for_status()


class ApiClient_People(_ApiClient_BaseSubPath):
    """Access the [people](https://app.voker.ai/docs/concepts/people) API endpoints.

    See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient_People)
    """

    def get(self, *, person_id: str) -> Person:
        """Retrieve a [person](https://app.voker.ai/docs/concepts/people)."""
        response = self._voker_client._request("GET", f"/api/v1/people/{person_id}")

        data = response.json()

        if response.status_code == 404:
            data = cast(PersonNotFoundResponse, data)
            raise PersonNotFoundError()

        response.raise_for_status()

        return Person.deserialize(data)

    def create(self, *, person_id: str, properties: dict[str, Any]) -> Person:
        """Create a person.

        See [Creating a Person](https://app.voker.ai/docs/concepts/people#creating-a-person) for more information.

        Parameters
        ----------
        person_id: str
            Unique identifier for the person.
        properties: dict[str, Any]
        """
        response = self._voker_client._request(
            "PUT",
            "/api/v1/people",
            json=CreatePersonPayload(person_id=person_id, properties=properties),
        )

        response.raise_for_status()

        data = response.json()

        return Person.deserialize(data)

    def update(self, *, person_id: str, properties: dict[str, Any]) -> Person:
        """Update a person."""
        response = self._voker_client._request(
            "PUT",
            f"/api/v1/people/{person_id}",
            json=UpdatePersonPayload(properties=properties),
        )

        data = response.json()

        if response.status_code == 404:
            data = cast(PersonNotFoundResponse, data)
            raise PersonNotFoundError()

        response.raise_for_status()

        return Person.deserialize(data)


def _git(cmd: str) -> str:
    return (
        subprocess.check_output(cmd.split(), stderr=subprocess.DEVNULL).decode().strip()
    )


class ApiClient_Fingerprints(_ApiClient_BaseSubPath):
    """Access [fingerprints](https://app.voker.ai/docs/concepts/events#fingerprints) related endpoints.

    See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient_Fingerprints)
    """

    def create(self) -> Fingerprint:
        """Create a fingerprint.

        See [Fingerprints](https://app.voker.ai/docs/concepts/events#fingerprints) for more information.
        """
        payload = CreateFingerprintPayload(
            language="python",
            language_version=sys.version,
            system=platform.platform(),
        )

        try:
            payload["openai_sdk_version"] = importlib.metadata.version("openai")
        except importlib.metadata.PackageNotFoundError:
            ...

        try:
            payload["anthropic_sdk_version"] = importlib.metadata.version("anthropic")
        except importlib.metadata.PackageNotFoundError:
            ...

        try:
            payload["gemini_sdk_version"] = importlib.metadata.version("google.genai")
        except importlib.metadata.PackageNotFoundError:
            ...

        try:
            payload["voker_sdk_version"] = importlib.metadata.version("voker")
        except importlib.metadata.PackageNotFoundError:
            ...

        payload["all_packages"] = {
            dist.metadata["Name"]: dist.version
            for dist in importlib.metadata.distributions()
        }

        try:
            payload["git_repository_url"] = _git("git config --get remote.origin.url")
            payload["git_branch"] = _git("git rev-parse --abbrev-ref HEAD")
            payload["git_commit_hash"] = _git("git rev-parse HEAD")
            payload["git_commit_date"] = _git("git show -s --format=%ci HEAD")
        except:
            ...

        response = self._voker_client._request(
            "PUT",
            "/api/v1/fingerprints",
            json=payload,
        )

        response.raise_for_status()

        data = response.json()

        return Fingerprint.deserialize(data)


class ApiClient_Agents(_ApiClient_BaseSubPath):
    """Access [agents](https://app.voker.ai/docs/concepts/agents#agents) related endpoints.

    See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient_Agents)
    """

    def create(self, *, agent_name: str, description: Optional[str] = None) -> Agent:
        """Create an agent.

        See [Creating an Agent](https://app.voker.ai/docs/concepts/agents#creating-agent) for more information.

        Parameters
        ----------
        agent_name: str
            Unique identifier for the agent.
        description: Optional[str]
        """
        payload = CreateAgentPayload(agent_name=agent_name)

        if description is not None:
            payload["description"] = description

        response = self._voker_client._request(
            "PUT",
            "/api/v1/agents",
            json=payload,
        )

        response.raise_for_status()

        data = response.json()

        return Agent.deserialize(data)

    def get(self, *, agent_name: str) -> Agent:
        """Get an agent."""
        response = self._voker_client._request("GET", f"/api/v1/agents/{agent_name}")

        data = response.json()

        if response.status_code == 404:
            data = cast(AgentNotFoundResponse, data)
            raise AgentNotFoundError()

        response.raise_for_status()

        return Agent.deserialize(data)


class ApiClient_AgentVersions(_ApiClient_BaseSubPath):
    """Access [agent versions](https://app.voker.ai/docs/concepts/agents#agent-versions) related endpoints.

    See [the reference documentation](https://app.voker.ai/docs/integrations/sdks/python#voker.api.client.ApiClient_AgentVersions)
    """

    def create(
        self,
        *,
        agent_name: str,
        agent_version_name: str,
        description: Optional[str] = None,
    ) -> AgentVersion:
        """Create an agent version.

        See [Creating an Agent Version](https://app.voker.ai/docs/concepts/agents#creating-agent-version) for more information.

        Parameters
        ----------
        agent_name: str
            The name of the existing agent that this version belongs to.
        agent_version_name: str
            Unique identifier for the agent version within its agent.
        description: Optional[str]
        """
        payload = CreateAgentVersionPayload(agent_version_name=agent_version_name)

        if description is not None:
            payload["description"] = description

        response = self._voker_client._request(
            "PUT",
            f"/api/v1/agents/{agent_name}/agent-versions",
            json=payload,
        )

        data = response.json()

        if response.status_code == 404:
            data = cast(AgentNotFoundResponse, data)
            raise AgentNotFoundError()

        response.raise_for_status()

        return AgentVersion.deserialize(data)

    def get(self, *, agent_name: str, agent_version_name: str) -> AgentVersion:
        """Get an agent version."""
        response = self._voker_client._request(
            "GET",
            f"/api/v1/agents/{agent_name}/agent-versions/{agent_version_name}",
        )

        data = response.json()

        if response.status_code == 404:
            data = cast(AgentNotFoundResponse | AgentVersionNotFoundResponse, data)

            if data["detail"]["type"] == "agent-not-found":
                raise AgentNotFoundError()
            elif data["detail"]["type"] == "agent-version-not-found":
                raise AgentVersionNotFoundError()

        response.raise_for_status()

        return AgentVersion.deserialize(data)
