"""
Snowflake View API.

The Snowflake View API is a REST API that you can use to access, update, and perform certain actions on View resource in a Snowflake database.  # noqa: E501

The version of the OpenAPI document: 0.0.1
Contact: support@snowflake.com
Generated by: https://openapi-generator.tech

Do not edit this file manually.
"""

import warnings

from typing import TYPE_CHECKING, Iterator, Optional, TypeVar, Union

from snowflake.core._common import CreateMode, SchemaObjectCollectionParent, SchemaObjectReferenceMixin
from snowflake.core._generated.api_client import StoredProcApiClient
from snowflake.core._internal.telemetry import api_telemetry
from snowflake.core._operation import PollingOperation, PollingOperations
from snowflake.core._utils import get_function_name_with_args, replace_function_name_in_name_with_args
from snowflake.core.tag import TagResource, TagValue
from snowflake.core.view._generated.models.tag_assignment import TagAssignment
from snowflake.core.view._generated.models.tag_reference import TagReference
from snowflake.core.view._generated.models.view import View

from .view_api import ViewApi


if TYPE_CHECKING:
    from snowflake.core.schema import SchemaResource


T = TypeVar("T")


class ViewCollectionBase(SchemaObjectCollectionParent["ViewResource"]):
    _identifier_requires_args: bool = False

    def __init__(self, schema: "SchemaResource", resource_class: type[T]) -> None:
        super().__init__(schema, resource_class)
        self._api = ViewApi(
            root=self.root, resource_class=self._ref_class, sproc_client=StoredProcApiClient(root=self.root)
        )

    @api_telemetry
    def create(
        self, view: View, mode: Optional[Union[CreateMode, str]] = None, copy_grants: Optional[bool] = None
    ) -> "ViewResource":  # noqa: F821
        """Create a view.

        Parameters
        __________
        view: View
             (required)
        mode: Union[CreateMode, str]
             Parameter allowing support for different modes of resource creation. Possible values include: - `errorIfExists`: Throws an error if you try to create a resource that already exists. - `orReplace`: Automatically replaces the existing resource with the current one. - `ifNotExists`: Creates a new resource when an alter is requested for a non-existent resource.
        copy_grants: bool
             Parameter to enable copy grants when creating the object.
        """
        if isinstance(mode, str):
            mode = CreateMode[mode].value

        self._api.create_view(
            self.database.name,
            self.schema.name,
            view=view,
            create_mode=mode,
            copy_grants=copy_grants,
            async_req=False,
        )
        if self._identifier_requires_args:
            return self._ref_class(get_function_name_with_args(view), self)
        return self._ref_class(view.name, self)

    @api_telemetry
    def create_async(
        self, view: View, mode: Optional[Union[CreateMode, str]] = None, copy_grants: Optional[bool] = None
    ) -> PollingOperation["ViewResource"]:  # noqa: F821
        """An asynchronous version of :func:`create`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        if isinstance(mode, str):
            mode = CreateMode[mode].value

        future = self._api.create_view(
            self.database.name,
            self.schema.name,
            view=view,
            create_mode=mode,
            copy_grants=copy_grants,
            async_req=True,
        )
        if self._identifier_requires_args:
            return PollingOperation(future, lambda _: self._ref_class(get_function_name_with_args(view), self))
        return PollingOperation(future, lambda _: self._ref_class(view if isinstance(view, str) else view.name, self))

    @api_telemetry
    def iter(
        self,
        *,
        like: Optional[str] = None,
        starts_with: Optional[str] = None,
        limit: Optional[int] = None,
        from_name: Optional[str] = None,
        deep: Optional[bool] = None,
        show_limit: Optional[int] = None,
    ) -> Iterator[View]:
        """List views.

        Parameters
        __________
        like: str
             Parameter to filter the command output by resource name. Uses case-insensitive pattern matching, with support for SQL wildcard characters.
        starts_with: str
             Parameter to filter the command output based on the string of characters that appear at the beginning of the object name. Uses case-sensitive pattern matching.
        limit: int
             Parameter to limit the maximum number of rows returned by a command.
        from_name: str
             Parameter to enable fetching rows only following the first row whose object name matches the specified string. Case-sensitive and does not have to be the full name.
        deep: bool
             Optionally includes dependency information of the view.
        """
        if show_limit is not None:
            if limit is not None:
                raise ValueError("Cannot specify both 'limit' and 'show_limit'. Use only one of them.")
            warnings.warn(DeprecationWarning("'show_limit' is deprecated, use 'limit' instead."))  # noqa: B028
            limit = show_limit

        resources = self._api.list_views(
            self.database.name,
            self.schema.name,
            like=like,
            starts_with=starts_with,
            show_limit=limit,
            from_name=from_name,
            deep=deep,
            async_req=False,
        )
        return iter(resources)

    @api_telemetry
    def iter_async(
        self,
        *,
        like: Optional[str] = None,
        starts_with: Optional[str] = None,
        limit: Optional[int] = None,
        from_name: Optional[str] = None,
        deep: Optional[bool] = None,
        show_limit: Optional[int] = None,
    ) -> PollingOperation[Iterator[View]]:
        """An asynchronous version of :func:`iter`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        if show_limit is not None:
            if limit is not None:
                raise ValueError("Cannot specify both 'limit' and 'show_limit'. Use only one of them.")
            warnings.warn(DeprecationWarning("'show_limit' is deprecated, use 'limit' instead."))  # noqa: B028
            limit = show_limit

        future = self._api.list_views(
            self.database.name,
            self.schema.name,
            like=like,
            starts_with=starts_with,
            show_limit=limit,
            from_name=from_name,
            deep=deep,
            async_req=True,
        )

        return PollingOperations.iterator(future)


class ViewResourceBase(SchemaObjectReferenceMixin["ViewCollection"]):
    _collection_class: ViewCollectionBase
    _identifier_requires_args: bool = False
    _plural_name: str

    def __init__(self, name: str, collection_class: ViewCollectionBase) -> None:
        self.collection = collection_class
        if self._identifier_requires_args:
            self.name_with_args = name
        else:
            self.name = name

    @api_telemetry
    def fetch(
        self,
    ) -> View:
        """Fetch a view.

        Parameters
        __________
        """
        result = self.collection._api.fetch_view(
            self.database.name,
            self.schema.name,
            self._identifier,
            async_req=False,
        )
        return result

    @api_telemetry
    def fetch_async(
        self,
    ) -> PollingOperation[View]:
        """An asynchronous version of :func:`fetch`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        future = self.collection._api.fetch_view(
            self.database.name,
            self.schema.name,
            self._identifier,
            async_req=True,
        )

        return PollingOperations.identity(future)

    @api_telemetry
    def get_tags(
        self,
        with_lineage: Optional[bool] = None,
    ) -> dict[TagResource, TagValue]:
        """Get the tag assignments for a view.

        Returns all tags assigned to a view. This operation requires an active warehouse.

        Parameters
        __________
        with_lineage: bool
             Parameter that specifies whether tag assignments inherited by the object from its ancestors in securable object hierarchy should be returned as well: - `true`: All tags assigned to this object should be returned, inheritance included. - `false`: Only tags explicitly assigned to this object should be returned.
        """
        result = self.collection._api.get_tags(
            self.database.name,
            self.schema.name,
            self._identifier,
            with_lineage=with_lineage,
            async_req=False,
        )
        result = dict(self._to_get_tags_tuple(tag_assignment) for tag_assignment in result)
        return result

    @api_telemetry
    def get_tags_async(
        self,
        with_lineage: Optional[bool] = None,
    ) -> PollingOperation[dict[TagResource, TagValue]]:
        """An asynchronous version of :func:`get_tags`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        future = self.collection._api.get_tags(
            self.database.name,
            self.schema.name,
            self._identifier,
            with_lineage=with_lineage,
            async_req=True,
        )
        return PollingOperation(
            future, lambda tag_assignments: dict(self._to_get_tags_tuple(ta) for ta in tag_assignments)
        )

    def _to_get_tags_tuple(self, ta: TagAssignment) -> tuple[TagResource, TagValue]:
        db = ta.tag_database
        schema = ta.tag_schema

        if db is None:
            raise ValueError("TagAssignment must have tag_database set.")

        if schema is None:
            raise ValueError("TagAssignment must have tag_schema set.")

        tag_resource = self.root.databases[db].schemas[schema].tags[ta.tag_name]
        tag_value = TagValue(ta.tag_value, ta.level)
        return tag_resource, tag_value

    @api_telemetry
    def set_tags(
        self,
        tags: dict[TagResource, TagValue],
        if_exists: Optional[bool] = None,
    ) -> None:
        """Set tags on a view.

        Parameters
        __________
        tags: dict[TagResource, TagValue]
             (required)
        if_exists: bool
             Parameter that specifies how to handle the request for a resource that does not exist: - `true`: The endpoint does not throw an error if the resource does not exist. It returns a 200 success response, but does not take any action on the resource. - `false`: The endpoint throws an error if the resource doesn't exist.
        """
        tag_assignment = [
            self._to_tag_assignment(tag_resource, tag_value) for [tag_resource, tag_value] in tags.items()
        ]
        self.collection._api.set_tags(
            self.database.name,
            self.schema.name,
            self._identifier,
            tag_assignment=tag_assignment,
            if_exists=if_exists,
            async_req=False,
        )

    @api_telemetry
    def set_tags_async(
        self,
        tags: dict[TagResource, TagValue],
        if_exists: Optional[bool] = None,
    ) -> PollingOperation[None]:
        """An asynchronous version of :func:`set_tags`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        tag_assignment = [
            self._to_tag_assignment(tag_resource, tag_value) for [tag_resource, tag_value] in tags.items()
        ]
        future = self.collection._api.set_tags(
            self.database.name,
            self.schema.name,
            self._identifier,
            tag_assignment=tag_assignment,
            if_exists=if_exists,
            async_req=True,
        )
        return PollingOperations.empty(future)

    @staticmethod
    def _to_tag_assignment(tag_resource: TagResource, tag_value: TagValue) -> TagAssignment:
        return TagAssignment(
            tag_name=tag_resource.name,
            tag_schema=tag_resource.schema.name,
            tag_database=tag_resource.database.name,
            tag_value=tag_value.value,
        )

    @api_telemetry
    def unset_tags(
        self,
        tag_resources: set[TagResource],
        if_exists: Optional[bool] = None,
    ) -> None:
        """Unset tags from a view.

        Parameters
        __________
        tag_resources: set[TagResource]
             (required)
        if_exists: bool
             Parameter that specifies how to handle the request for a resource that does not exist: - `true`: The endpoint does not throw an error if the resource does not exist. It returns a 200 success response, but does not take any action on the resource. - `false`: The endpoint throws an error if the resource doesn't exist.
        """
        tag_reference = [self._to_tag_reference(tag_resource) for tag_resource in tag_resources]
        self.collection._api.unset_tags(
            self.database.name,
            self.schema.name,
            self._identifier,
            tag_reference=tag_reference,
            if_exists=if_exists,
            async_req=False,
        )

    @api_telemetry
    def unset_tags_async(
        self,
        tag_resources: set[TagResource],
        if_exists: Optional[bool] = None,
    ) -> PollingOperation[None]:
        """An asynchronous version of :func:`unset_tags`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        tag_reference = [self._to_tag_reference(tag_resource) for tag_resource in tag_resources]
        future = self.collection._api.unset_tags(
            self.database.name,
            self.schema.name,
            self._identifier,
            tag_reference=tag_reference,
            if_exists=if_exists,
            async_req=True,
        )
        return PollingOperations.empty(future)

    @staticmethod
    def _to_tag_reference(resource: TagResource) -> TagReference:
        return TagReference(
            tag_name=resource.name,
            tag_schema=resource.schema.name,
            tag_database=resource.database.name,
        )

    @api_telemetry
    def drop(
        self,
        if_exists: Optional[bool] = None,
    ) -> None:
        """Delete a view.

        Parameters
        __________
        if_exists: bool
             Parameter that specifies how to handle the request for a resource that does not exist: - `true`: The endpoint does not throw an error if the resource does not exist. It returns a 200 success response, but does not take any action on the resource. - `false`: The endpoint throws an error if the resource doesn't exist.
        """
        self.collection._api.delete_view(
            self.database.name,
            self.schema.name,
            self._identifier,
            if_exists=if_exists,
            async_req=False,
        )

    @api_telemetry
    def drop_async(
        self,
        if_exists: Optional[bool] = None,
    ) -> PollingOperation[None]:
        """An asynchronous version of :func:`drop`.

        Refer to :class:`~snowflake.core.PollingOperation` for more information on asynchronous execution and
        the return type.
        """
        future = self.collection._api.delete_view(
            self.database.name,
            self.schema.name,
            self._identifier,
            if_exists=if_exists,
            async_req=True,
        )
        return PollingOperations.empty(future)

    @property
    def _identifier(self) -> str:
        """Get the identifier for this resource."""
        if self._identifier_requires_args:
            return self.name_with_args
        return self.name

    def _set_new_name(self, target_name: str) -> None:
        """Set a new name for the resource."""
        if self._identifier_requires_args:
            old_name = self.name_with_args
            self.name_with_args = replace_function_name_in_name_with_args(self.name_with_args, target_name)
        else:
            old_name = self.name
            self.name = target_name

        # Update the collection reference to point to the new name
        self.collection.update_reference(old_name=old_name, new_name=self._identifier, resource=self)
