# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you 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.

from typing import Optional

from pypaimon.api.rest_api import RESTApi
from pypaimon.api.rest_util import RESTUtil
from pypaimon.catalog.catalog_context import CatalogContext
from pypaimon.catalog.catalog_loader import CatalogLoader
from pypaimon.common.identifier import Identifier
from pypaimon.common.json_util import JSON
from pypaimon.common.options.config import CatalogOptions
from pypaimon.snapshot.catalog_snapshot_commit import CatalogSnapshotCommit
from pypaimon.snapshot.renaming_snapshot_commit import RenamingSnapshotCommit
from pypaimon.snapshot.snapshot_commit import SnapshotCommit
from pypaimon.snapshot.snapshot_loader import SnapshotLoader


class CatalogEnvironment:

    _READ_VIA_OPTION = RESTApi.HEADER_PREFIX + RESTApi.READ_VIA_HEADER

    def __init__(
            self,
            identifier: Optional[Identifier] = None,
            uuid: Optional[str] = None,
            catalog_loader: Optional[CatalogLoader] = None,
            supports_version_management: bool = False
    ):
        self.identifier = identifier
        self.uuid = uuid
        self.catalog_loader = catalog_loader
        self.supports_version_management = supports_version_management

    def snapshot_commit(self, snapshot_manager) -> Optional[SnapshotCommit]:
        """
        Create a SnapshotCommit instance based on the catalog environment configuration.

        Args:
            snapshot_manager: The SnapshotManager instance

        Returns:
            SnapshotCommit instance or None
        """
        if self.catalog_loader is not None and self.supports_version_management:
            # Use catalog-based snapshot commit when catalog loader is available
            # and version management is supported
            catalog = self.catalog_loader.load()
            return CatalogSnapshotCommit(catalog, self.identifier, self.uuid)
        else:
            # Use file renaming-based snapshot commit
            # In a full implementation, this would use a proper lock factory
            # to create locks based on the catalog lock context
            return RenamingSnapshotCommit(snapshot_manager)

    def catalog_table_rollback(self):
        """Create a TableRollback instance based on the catalog environment.

        Returns a TableRollback that delegates to catalog.rollback_to
        when catalog_loader is available and version management is supported.
        Returns None otherwise (fallback to local file cleanup).

        Returns:
            A TableRollback instance, or None.
        """
        if self.catalog_loader is not None and self.supports_version_management:
            from pypaimon.catalog.table_rollback import TableRollback
            catalog = self.catalog_loader.load()
            return TableRollback(catalog, self.identifier)
        return None

    def snapshot_loader(self) -> Optional[SnapshotLoader]:
        """Create a SnapshotLoader instance based on the catalog environment.

        Returns:
            SnapshotLoader instance if catalog_loader is available, None otherwise
        """
        if self.catalog_loader is not None:
            return SnapshotLoader(self.catalog_loader, self.identifier)
        return None

    def catalog_context(self) -> Optional[CatalogContext]:
        if self.catalog_loader is None:
            return None
        context = getattr(self.catalog_loader, "context", None)
        return context() if callable(context) else None

    def dependency_read_context(self) -> Optional[CatalogContext]:
        context = self.catalog_context()
        if self.identifier is None or context is None:
            return context

        from pypaimon.catalog.rest.rest_catalog_loader import RESTCatalogLoader

        rest_catalog = (
            isinstance(self.catalog_loader, RESTCatalogLoader)
            or context.options.get(CatalogOptions.METASTORE) == "rest"
        )
        if not rest_catalog:
            return context
        if context.options.contains_key(self._READ_VIA_OPTION):
            return context

        dependency_options = context.options.copy()
        if not dependency_options.contains(CatalogOptions.METASTORE):
            dependency_options.set(CatalogOptions.METASTORE, "rest")
        dependency_options.to_map()[self._READ_VIA_OPTION] = RESTUtil.encode_string(
            JSON.to_json(self.identifier, separators=(",", ":"))
        )
        return CatalogContext.create(
            dependency_options,
            context.hadoop_conf,
            context.prefer_io_loader,
            context.fallback_io_loader,
        )

    def copy(self, identifier: Identifier) -> 'CatalogEnvironment':
        """
        Create a copy of this CatalogEnvironment with a different identifier.

        Args:
            identifier: The new identifier

        Returns:
            A new CatalogEnvironment instance
        """
        return CatalogEnvironment(
            identifier=identifier,
            uuid=self.uuid,
            catalog_loader=self.catalog_loader,
            supports_version_management=self.supports_version_management
        )

    @staticmethod
    def empty() -> 'CatalogEnvironment':
        """
        Create an empty CatalogEnvironment with default values.

        Returns:
            An empty CatalogEnvironment instance
        """
        return CatalogEnvironment(
            identifier=None,
            uuid=None,
            catalog_loader=None,
            supports_version_management=False
        )

    def table_query_auth(self, options, identifier):
        if not options.query_auth_enabled or self.catalog_loader is None:
            return None
        return _TableQueryAuthFn(self.catalog_loader, identifier)


class _TableQueryAuthFn:

    def __init__(self, catalog_loader, identifier):
        self._catalog_loader = catalog_loader
        self._identifier = identifier

    def __call__(self, select):
        catalog = self._catalog_loader.load()
        return catalog.auth_table_query(self._identifier, select)
