# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

from __future__ import annotations

import os
from typing import TYPE_CHECKING, Any, Dict, Mapping, cast
from typing_extensions import Self, Literal, override

import httpx

from . import _exceptions
from ._qs import Querystring
from ._types import (
    Omit,
    Timeout,
    NotGiven,
    Transport,
    ProxiesTypes,
    RequestOptions,
    not_given,
)
from ._utils import is_given, get_async_library
from ._compat import cached_property
from ._models import SecurityOptions
from ._version import __version__
from ._streaming import Stream as Stream, AsyncStream as AsyncStream
from ._exceptions import VillageError, APIStatusError
from ._base_client import (
    DEFAULT_MAX_RETRIES,
    SyncAPIClient,
    AsyncAPIClient,
)

if TYPE_CHECKING:
    from .resources import web, edgar, email, quartr, company
    from .resources.web import WebResource, AsyncWebResource
    from .resources.email import EmailResource, AsyncEmailResource
    from .resources.edgar.edgar import EdgarResource, AsyncEdgarResource
    from .resources.quartr.quartr import QuartrResource, AsyncQuartrResource
    from .resources.company.company import CompanyResource, AsyncCompanyResource

__all__ = [
    "ENVIRONMENTS",
    "Timeout",
    "Transport",
    "ProxiesTypes",
    "RequestOptions",
    "Village",
    "AsyncVillage",
    "Client",
    "AsyncClient",
]

ENVIRONMENTS: Dict[str, str] = {
    "production": "https://api.village.dev/api/v1",
    "staging": "https://staging-api.village.dev/api/v1",
}


class Village(SyncAPIClient):
    # client options
    api_key: str

    _environment: Literal["production", "staging"] | NotGiven

    def __init__(
        self,
        *,
        api_key: str | None = None,
        environment: Literal["production", "staging"] | NotGiven = not_given,
        base_url: str | httpx.URL | None | NotGiven = not_given,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
        http_client: httpx.Client | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new synchronous Village client instance.

        This automatically infers the `api_key` argument from the `VILLAGE_API_KEY` environment variable if it is not provided.
        """
        if api_key is None:
            api_key = os.environ.get("VILLAGE_API_KEY")
        if api_key is None:
            raise VillageError(
                "The api_key client option must be set either by passing api_key to the client or by setting the VILLAGE_API_KEY environment variable"
            )
        self.api_key = api_key

        self._environment = environment

        base_url_env = os.environ.get("VILLAGE_BASE_URL")
        if is_given(base_url) and base_url is not None:
            # cast required because mypy doesn't understand the type narrowing
            base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
        elif is_given(environment):
            if base_url_env and base_url is not None:
                raise ValueError(
                    "Ambiguous URL; The `VILLAGE_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                )

            try:
                base_url = ENVIRONMENTS[environment]
            except KeyError as exc:
                raise ValueError(f"Unknown environment: {environment}") from exc
        elif base_url_env is not None:
            base_url = base_url_env
        else:
            self._environment = environment = "production"

            try:
                base_url = ENVIRONMENTS[environment]
            except KeyError as exc:
                raise ValueError(f"Unknown environment: {environment}") from exc

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            _strict_response_validation=_strict_response_validation,
        )

    @cached_property
    def company(self) -> CompanyResource:
        """
        Look up companies by ticker and browse their EDGAR filings and Quartr documents.
        """
        from .resources.company import CompanyResource

        return CompanyResource(self)

    @cached_property
    def edgar(self) -> EdgarResource:
        """Direct access to SEC EDGAR filings and documents by accession number."""
        from .resources.edgar import EdgarResource

        return EdgarResource(self)

    @cached_property
    def quartr(self) -> QuartrResource:
        """Direct access to Quartr events and IR documents by ID."""
        from .resources.quartr import QuartrResource

        return QuartrResource(self)

    @cached_property
    def email(self) -> EmailResource:
        """Send emails via AWS SES."""
        from .resources.email import EmailResource

        return EmailResource(self)

    @cached_property
    def web(self) -> WebResource:
        """Search and scrape webpages with finance-oriented defaults."""
        from .resources.web import WebResource

        return WebResource(self)

    @cached_property
    def with_raw_response(self) -> VillageWithRawResponse:
        return VillageWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> VillageWithStreamedResponse:
        return VillageWithStreamedResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="comma")

    @override
    def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:
        return {
            **(self._api_key_header if security.get("api_key_header", False) else {}),
        }

    @property
    def _api_key_header(self) -> dict[str, str]:
        api_key = self.api_key
        return {"X-API-Key": api_key}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": "false",
            **self._custom_headers,
        }

    def copy(
        self,
        *,
        api_key: str | None = None,
        environment: Literal["production", "staging"] | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx.Client | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client
        return self.__class__(
            api_key=api_key or self.api_key,
            base_url=base_url or self.base_url,
            environment=environment or self._environment,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class AsyncVillage(AsyncAPIClient):
    # client options
    api_key: str

    _environment: Literal["production", "staging"] | NotGiven

    def __init__(
        self,
        *,
        api_key: str | None = None,
        environment: Literal["production", "staging"] | NotGiven = not_given,
        base_url: str | httpx.URL | None | NotGiven = not_given,
        timeout: float | Timeout | None | NotGiven = not_given,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        # Configure a custom httpx client.
        # We provide a `DefaultAsyncHttpxClient` class that you can pass to retain the default values we use for `limits`, `timeout` & `follow_redirects`.
        # See the [httpx documentation](https://www.python-httpx.org/api/#asyncclient) for more details.
        http_client: httpx.AsyncClient | None = None,
        # Enable or disable schema validation for data returned by the API.
        # When enabled an error APIResponseValidationError is raised
        # if the API responds with invalid data for the expected schema.
        #
        # This parameter may be removed or changed in the future.
        # If you rely on this feature, please open a GitHub issue
        # outlining your use-case to help us decide if it should be
        # part of our public interface in the future.
        _strict_response_validation: bool = False,
    ) -> None:
        """Construct a new async AsyncVillage client instance.

        This automatically infers the `api_key` argument from the `VILLAGE_API_KEY` environment variable if it is not provided.
        """
        if api_key is None:
            api_key = os.environ.get("VILLAGE_API_KEY")
        if api_key is None:
            raise VillageError(
                "The api_key client option must be set either by passing api_key to the client or by setting the VILLAGE_API_KEY environment variable"
            )
        self.api_key = api_key

        self._environment = environment

        base_url_env = os.environ.get("VILLAGE_BASE_URL")
        if is_given(base_url) and base_url is not None:
            # cast required because mypy doesn't understand the type narrowing
            base_url = cast("str | httpx.URL", base_url)  # pyright: ignore[reportUnnecessaryCast]
        elif is_given(environment):
            if base_url_env and base_url is not None:
                raise ValueError(
                    "Ambiguous URL; The `VILLAGE_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None",
                )

            try:
                base_url = ENVIRONMENTS[environment]
            except KeyError as exc:
                raise ValueError(f"Unknown environment: {environment}") from exc
        elif base_url_env is not None:
            base_url = base_url_env
        else:
            self._environment = environment = "production"

            try:
                base_url = ENVIRONMENTS[environment]
            except KeyError as exc:
                raise ValueError(f"Unknown environment: {environment}") from exc

        super().__init__(
            version=__version__,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            http_client=http_client,
            custom_headers=default_headers,
            custom_query=default_query,
            _strict_response_validation=_strict_response_validation,
        )

    @cached_property
    def company(self) -> AsyncCompanyResource:
        """
        Look up companies by ticker and browse their EDGAR filings and Quartr documents.
        """
        from .resources.company import AsyncCompanyResource

        return AsyncCompanyResource(self)

    @cached_property
    def edgar(self) -> AsyncEdgarResource:
        """Direct access to SEC EDGAR filings and documents by accession number."""
        from .resources.edgar import AsyncEdgarResource

        return AsyncEdgarResource(self)

    @cached_property
    def quartr(self) -> AsyncQuartrResource:
        """Direct access to Quartr events and IR documents by ID."""
        from .resources.quartr import AsyncQuartrResource

        return AsyncQuartrResource(self)

    @cached_property
    def email(self) -> AsyncEmailResource:
        """Send emails via AWS SES."""
        from .resources.email import AsyncEmailResource

        return AsyncEmailResource(self)

    @cached_property
    def web(self) -> AsyncWebResource:
        """Search and scrape webpages with finance-oriented defaults."""
        from .resources.web import AsyncWebResource

        return AsyncWebResource(self)

    @cached_property
    def with_raw_response(self) -> AsyncVillageWithRawResponse:
        return AsyncVillageWithRawResponse(self)

    @cached_property
    def with_streaming_response(self) -> AsyncVillageWithStreamedResponse:
        return AsyncVillageWithStreamedResponse(self)

    @property
    @override
    def qs(self) -> Querystring:
        return Querystring(array_format="comma")

    @override
    def _auth_headers(self, security: SecurityOptions) -> dict[str, str]:
        return {
            **(self._api_key_header if security.get("api_key_header", False) else {}),
        }

    @property
    def _api_key_header(self) -> dict[str, str]:
        api_key = self.api_key
        return {"X-API-Key": api_key}

    @property
    @override
    def default_headers(self) -> dict[str, str | Omit]:
        return {
            **super().default_headers,
            "X-Stainless-Async": f"async:{get_async_library()}",
            **self._custom_headers,
        }

    def copy(
        self,
        *,
        api_key: str | None = None,
        environment: Literal["production", "staging"] | None = None,
        base_url: str | httpx.URL | None = None,
        timeout: float | Timeout | None | NotGiven = not_given,
        http_client: httpx.AsyncClient | None = None,
        max_retries: int | NotGiven = not_given,
        default_headers: Mapping[str, str] | None = None,
        set_default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        set_default_query: Mapping[str, object] | None = None,
        _extra_kwargs: Mapping[str, Any] = {},
    ) -> Self:
        """
        Create a new client instance re-using the same options given to the current client with optional overriding.
        """
        if default_headers is not None and set_default_headers is not None:
            raise ValueError("The `default_headers` and `set_default_headers` arguments are mutually exclusive")

        if default_query is not None and set_default_query is not None:
            raise ValueError("The `default_query` and `set_default_query` arguments are mutually exclusive")

        headers = self._custom_headers
        if default_headers is not None:
            headers = {**headers, **default_headers}
        elif set_default_headers is not None:
            headers = set_default_headers

        params = self._custom_query
        if default_query is not None:
            params = {**params, **default_query}
        elif set_default_query is not None:
            params = set_default_query

        http_client = http_client or self._client
        return self.__class__(
            api_key=api_key or self.api_key,
            base_url=base_url or self.base_url,
            environment=environment or self._environment,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            http_client=http_client,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers=headers,
            default_query=params,
            **_extra_kwargs,
        )

    # Alias for `copy` for nicer inline usage, e.g.
    # client.with_options(timeout=10).foo.create(...)
    with_options = copy

    @override
    def _make_status_error(
        self,
        err_msg: str,
        *,
        body: object,
        response: httpx.Response,
    ) -> APIStatusError:
        if response.status_code == 400:
            return _exceptions.BadRequestError(err_msg, response=response, body=body)

        if response.status_code == 401:
            return _exceptions.AuthenticationError(err_msg, response=response, body=body)

        if response.status_code == 403:
            return _exceptions.PermissionDeniedError(err_msg, response=response, body=body)

        if response.status_code == 404:
            return _exceptions.NotFoundError(err_msg, response=response, body=body)

        if response.status_code == 409:
            return _exceptions.ConflictError(err_msg, response=response, body=body)

        if response.status_code == 422:
            return _exceptions.UnprocessableEntityError(err_msg, response=response, body=body)

        if response.status_code == 429:
            return _exceptions.RateLimitError(err_msg, response=response, body=body)

        if response.status_code >= 500:
            return _exceptions.InternalServerError(err_msg, response=response, body=body)
        return APIStatusError(err_msg, response=response, body=body)


class VillageWithRawResponse:
    _client: Village

    def __init__(self, client: Village) -> None:
        self._client = client

    @cached_property
    def company(self) -> company.CompanyResourceWithRawResponse:
        """
        Look up companies by ticker and browse their EDGAR filings and Quartr documents.
        """
        from .resources.company import CompanyResourceWithRawResponse

        return CompanyResourceWithRawResponse(self._client.company)

    @cached_property
    def edgar(self) -> edgar.EdgarResourceWithRawResponse:
        """Direct access to SEC EDGAR filings and documents by accession number."""
        from .resources.edgar import EdgarResourceWithRawResponse

        return EdgarResourceWithRawResponse(self._client.edgar)

    @cached_property
    def quartr(self) -> quartr.QuartrResourceWithRawResponse:
        """Direct access to Quartr events and IR documents by ID."""
        from .resources.quartr import QuartrResourceWithRawResponse

        return QuartrResourceWithRawResponse(self._client.quartr)

    @cached_property
    def email(self) -> email.EmailResourceWithRawResponse:
        """Send emails via AWS SES."""
        from .resources.email import EmailResourceWithRawResponse

        return EmailResourceWithRawResponse(self._client.email)

    @cached_property
    def web(self) -> web.WebResourceWithRawResponse:
        """Search and scrape webpages with finance-oriented defaults."""
        from .resources.web import WebResourceWithRawResponse

        return WebResourceWithRawResponse(self._client.web)


class AsyncVillageWithRawResponse:
    _client: AsyncVillage

    def __init__(self, client: AsyncVillage) -> None:
        self._client = client

    @cached_property
    def company(self) -> company.AsyncCompanyResourceWithRawResponse:
        """
        Look up companies by ticker and browse their EDGAR filings and Quartr documents.
        """
        from .resources.company import AsyncCompanyResourceWithRawResponse

        return AsyncCompanyResourceWithRawResponse(self._client.company)

    @cached_property
    def edgar(self) -> edgar.AsyncEdgarResourceWithRawResponse:
        """Direct access to SEC EDGAR filings and documents by accession number."""
        from .resources.edgar import AsyncEdgarResourceWithRawResponse

        return AsyncEdgarResourceWithRawResponse(self._client.edgar)

    @cached_property
    def quartr(self) -> quartr.AsyncQuartrResourceWithRawResponse:
        """Direct access to Quartr events and IR documents by ID."""
        from .resources.quartr import AsyncQuartrResourceWithRawResponse

        return AsyncQuartrResourceWithRawResponse(self._client.quartr)

    @cached_property
    def email(self) -> email.AsyncEmailResourceWithRawResponse:
        """Send emails via AWS SES."""
        from .resources.email import AsyncEmailResourceWithRawResponse

        return AsyncEmailResourceWithRawResponse(self._client.email)

    @cached_property
    def web(self) -> web.AsyncWebResourceWithRawResponse:
        """Search and scrape webpages with finance-oriented defaults."""
        from .resources.web import AsyncWebResourceWithRawResponse

        return AsyncWebResourceWithRawResponse(self._client.web)


class VillageWithStreamedResponse:
    _client: Village

    def __init__(self, client: Village) -> None:
        self._client = client

    @cached_property
    def company(self) -> company.CompanyResourceWithStreamingResponse:
        """
        Look up companies by ticker and browse their EDGAR filings and Quartr documents.
        """
        from .resources.company import CompanyResourceWithStreamingResponse

        return CompanyResourceWithStreamingResponse(self._client.company)

    @cached_property
    def edgar(self) -> edgar.EdgarResourceWithStreamingResponse:
        """Direct access to SEC EDGAR filings and documents by accession number."""
        from .resources.edgar import EdgarResourceWithStreamingResponse

        return EdgarResourceWithStreamingResponse(self._client.edgar)

    @cached_property
    def quartr(self) -> quartr.QuartrResourceWithStreamingResponse:
        """Direct access to Quartr events and IR documents by ID."""
        from .resources.quartr import QuartrResourceWithStreamingResponse

        return QuartrResourceWithStreamingResponse(self._client.quartr)

    @cached_property
    def email(self) -> email.EmailResourceWithStreamingResponse:
        """Send emails via AWS SES."""
        from .resources.email import EmailResourceWithStreamingResponse

        return EmailResourceWithStreamingResponse(self._client.email)

    @cached_property
    def web(self) -> web.WebResourceWithStreamingResponse:
        """Search and scrape webpages with finance-oriented defaults."""
        from .resources.web import WebResourceWithStreamingResponse

        return WebResourceWithStreamingResponse(self._client.web)


class AsyncVillageWithStreamedResponse:
    _client: AsyncVillage

    def __init__(self, client: AsyncVillage) -> None:
        self._client = client

    @cached_property
    def company(self) -> company.AsyncCompanyResourceWithStreamingResponse:
        """
        Look up companies by ticker and browse their EDGAR filings and Quartr documents.
        """
        from .resources.company import AsyncCompanyResourceWithStreamingResponse

        return AsyncCompanyResourceWithStreamingResponse(self._client.company)

    @cached_property
    def edgar(self) -> edgar.AsyncEdgarResourceWithStreamingResponse:
        """Direct access to SEC EDGAR filings and documents by accession number."""
        from .resources.edgar import AsyncEdgarResourceWithStreamingResponse

        return AsyncEdgarResourceWithStreamingResponse(self._client.edgar)

    @cached_property
    def quartr(self) -> quartr.AsyncQuartrResourceWithStreamingResponse:
        """Direct access to Quartr events and IR documents by ID."""
        from .resources.quartr import AsyncQuartrResourceWithStreamingResponse

        return AsyncQuartrResourceWithStreamingResponse(self._client.quartr)

    @cached_property
    def email(self) -> email.AsyncEmailResourceWithStreamingResponse:
        """Send emails via AWS SES."""
        from .resources.email import AsyncEmailResourceWithStreamingResponse

        return AsyncEmailResourceWithStreamingResponse(self._client.email)

    @cached_property
    def web(self) -> web.AsyncWebResourceWithStreamingResponse:
        """Search and scrape webpages with finance-oriented defaults."""
        from .resources.web import AsyncWebResourceWithStreamingResponse

        return AsyncWebResourceWithStreamingResponse(self._client.web)


Client = Village

AsyncClient = AsyncVillage
