#
# 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.
#
import argparse
from abc import ABC
from typing import Callable, Optional, Any

from apache_polaris.cli.constants import Commands, Arguments
from apache_polaris.cli.options.parser import Parser
from apache_polaris.sdk.management import PolarisDefaultApi


class Command(ABC):
    """
    An abstract base class for commands. Implementations are expected to override the class methods `validate` and
    `execute`. The static method `Command.from_options` can be used to parse a argparse Namespace into the appropriate
    Command implementation if one exists.
    """

    @staticmethod
    def from_options(options: argparse.Namespace) -> "Command":
        def options_get(key: str, f: Callable[[Any], Any] = lambda x: x) -> Any:
            return f(getattr(options, key)) if hasattr(options, key) else None

        properties = Parser.parse_properties(options_get(Arguments.PROPERTY))
        set_properties = Parser.parse_properties(options_get(Arguments.SET_PROPERTY))
        remove_properties = options_get(Arguments.REMOVE_PROPERTY)
        catalog_client_scopes = options_get(Arguments.CATALOG_CLIENT_SCOPE)
        parameters = Parser.parse_properties(options_get(Arguments.PARAMETERS))

        command: Optional[Command] = None
        if options.command == Commands.CATALOGS:
            from apache_polaris.cli.command.catalogs import CatalogsCommand

            command = CatalogsCommand(
                options_get(f"{Commands.CATALOGS}_subcommand"),
                catalog_type=options_get(Arguments.TYPE),
                default_base_location=options_get(Arguments.DEFAULT_BASE_LOCATION),
                storage_type=options_get(Arguments.STORAGE_TYPE),
                allowed_locations=options_get(Arguments.ALLOWED_LOCATION),
                role_arn=options_get(Arguments.ROLE_ARN),
                external_id=options_get(Arguments.EXTERNAL_ID),
                user_arn=options_get(Arguments.USER_ARN),
                region=options_get(Arguments.REGION),
                tenant_id=options_get(Arguments.TENANT_ID),
                multi_tenant_app_name=options_get(Arguments.MULTI_TENANT_APP_NAME),
                hierarchical=options_get(Arguments.HIERARCHICAL),
                consent_url=options_get(Arguments.CONSENT_URL),
                service_account=options_get(Arguments.SERVICE_ACCOUNT),
                catalog_name=options_get(Arguments.CATALOG),
                properties={} if properties is None else properties,
                set_properties={} if set_properties is None else set_properties,
                hadoop_warehouse=options_get(Arguments.HADOOP_WAREHOUSE),
                hive_warehouse=options_get(Arguments.HIVE_WAREHOUSE),
                iceberg_remote_catalog_name=options_get(
                    Arguments.ICEBERG_REMOTE_CATALOG_NAME
                ),
                remove_properties=[]
                if remove_properties is None
                else remove_properties,
                endpoint=options_get(Arguments.ENDPOINT),
                endpoint_internal=options_get(Arguments.ENDPOINT_INTERNAL),
                sts_endpoint=options_get(Arguments.STS_ENDPOINT),
                sts_unavailable=options_get(Arguments.STS_UNAVAILABLE),
                kms_unavailable=options_get(Arguments.KMS_UNAVAILABLE),
                path_style_access=options_get(Arguments.PATH_STYLE_ACCESS),
                current_kms_key=options_get(Arguments.KMS_KEY_CURRENT),
                allowed_kms_keys=options_get(Arguments.KMS_KEY_ALLOWED),
                catalog_connection_type=options_get(Arguments.CATALOG_CONNECTION_TYPE),
                catalog_authentication_type=options_get(
                    Arguments.CATALOG_AUTHENTICATION_TYPE
                ),
                catalog_service_identity_type=options_get(
                    Arguments.CATALOG_SERVICE_IDENTITY_TYPE
                ),
                catalog_service_identity_iam_arn=options_get(
                    Arguments.CATALOG_SERVICE_IDENTITY_IAM_ARN
                ),
                catalog_uri=options_get(Arguments.CATALOG_URI),
                catalog_token_uri=options_get(Arguments.CATALOG_TOKEN_URI),
                catalog_client_id=options_get(Arguments.CATALOG_CLIENT_ID),
                catalog_client_secret=options_get(Arguments.CATALOG_CLIENT_SECRET),
                catalog_client_scopes=[]
                if catalog_client_scopes is None
                else catalog_client_scopes,
                catalog_bearer_token=options_get(Arguments.CATALOG_BEARER_TOKEN),
                catalog_role_arn=options_get(Arguments.CATALOG_ROLE_ARN),
                catalog_role_session_name=options_get(
                    Arguments.CATALOG_ROLE_SESSION_NAME
                ),
                catalog_external_id=options_get(Arguments.CATALOG_EXTERNAL_ID),
                catalog_signing_region=options_get(Arguments.CATALOG_SIGNING_REGION),
                catalog_signing_name=options_get(Arguments.CATALOG_SIGNING_NAME),
            )
        elif options.command == Commands.PRINCIPALS:
            from apache_polaris.cli.command.principals import PrincipalsCommand

            command = PrincipalsCommand(
                options_get(f"{Commands.PRINCIPALS}_subcommand"),
                type=options_get(Arguments.TYPE),
                principal_name=options_get(Arguments.PRINCIPAL),
                client_id=options_get(Arguments.CLIENT_ID),
                principal_role=options_get(Arguments.PRINCIPAL_ROLE),
                properties={} if properties is None else properties,
                set_properties={} if set_properties is None else set_properties,
                remove_properties=[]
                if remove_properties is None
                else remove_properties,
                new_client_id=options_get(Arguments.NEW_CLIENT_ID),
                new_client_secret=options_get(Arguments.NEW_CLIENT_SECRET),
            )
        elif options.command == Commands.PRINCIPAL_ROLES:
            from apache_polaris.cli.command.principal_roles import PrincipalRolesCommand

            command = PrincipalRolesCommand(
                options_get(f"{Commands.PRINCIPAL_ROLES}_subcommand"),
                principal_role_name=options_get(Arguments.PRINCIPAL_ROLE),
                principal_name=options_get(Arguments.PRINCIPAL),
                catalog_name=options_get(Arguments.CATALOG),
                catalog_role_name=options_get(Arguments.CATALOG_ROLE),
                properties={} if properties is None else properties,
                set_properties={} if set_properties is None else set_properties,
                remove_properties=[]
                if remove_properties is None
                else remove_properties,
            )
        elif options.command == Commands.CATALOG_ROLES:
            from apache_polaris.cli.command.catalog_roles import CatalogRolesCommand

            command = CatalogRolesCommand(
                options_get(f"{Commands.CATALOG_ROLES}_subcommand"),
                catalog_name=options_get(Arguments.CATALOG),
                catalog_role_name=options_get(Arguments.CATALOG_ROLE),
                principal_role_name=options_get(Arguments.PRINCIPAL_ROLE),
                properties={} if properties is None else properties,
                set_properties={} if set_properties is None else set_properties,
                remove_properties=[]
                if remove_properties is None
                else remove_properties,
            )
        elif options.command == Commands.PRIVILEGES:
            from apache_polaris.cli.command.privileges import PrivilegesCommand

            subcommand = options_get(f"{Commands.PRIVILEGES}_subcommand")
            command = PrivilegesCommand(
                subcommand,
                action=options_get(f"{subcommand}_subcommand"),
                catalog_name=options_get(Arguments.CATALOG),
                catalog_role_name=options_get(Arguments.CATALOG_ROLE),
                namespace=options_get(
                    Arguments.NAMESPACE, lambda s: s.split(".") if s else None
                ),
                view=options_get(Arguments.VIEW),
                table=options_get(Arguments.TABLE),
                privilege=options_get(Arguments.PRIVILEGE),
                cascade=options_get(Arguments.CASCADE),
            )
        elif options.command == Commands.NAMESPACES:
            from apache_polaris.cli.command.namespaces import NamespacesCommand

            subcommand = options_get(f"{Commands.NAMESPACES}_subcommand")
            command = NamespacesCommand(
                subcommand,
                catalog=options_get(Arguments.CATALOG),
                namespace=options_get(Arguments.NAMESPACE, lambda s: s.split(".")),
                parent=options_get(
                    Arguments.PARENT, lambda s: s.split(".") if s else None
                ),
                location=options_get(Arguments.LOCATION),
                properties=properties,
            )
        elif options.command == Commands.PROFILES:
            from apache_polaris.cli.command.profiles import ProfilesCommand

            subcommand = options_get(f"{Commands.PROFILES}_subcommand")
            command = ProfilesCommand(
                subcommand, profile_name=options_get(Arguments.PROFILE)
            )
        elif options.command == Commands.POLICIES:
            from apache_polaris.cli.command.policies import PoliciesCommand

            subcommand = options_get(f"{Commands.POLICIES}_subcommand")
            command = PoliciesCommand(
                subcommand,
                catalog_name=options_get(Arguments.CATALOG),
                namespace=options_get(Arguments.NAMESPACE),
                policy_name=options_get(Arguments.POLICY),
                policy_file=options_get(Arguments.POLICY_FILE),
                policy_type=options_get(Arguments.POLICY_TYPE),
                policy_description=options_get(Arguments.POLICY_DESCRIPTION),
                target_name=options_get(Arguments.TARGET_NAME),
                parameters={} if parameters is None else parameters,
                detach_all=options_get(Arguments.DETACH_ALL),
                applicable=options_get(Arguments.APPLICABLE),
                attachment_type=options_get(Arguments.ATTACHMENT_TYPE),
                attachment_path=options_get(Arguments.ATTACHMENT_PATH),
            )
        elif options.command == Commands.SETUP:
            from apache_polaris.cli.command.setup import SetupCommand

            subcommand = options_get(f"{Commands.SETUP}_subcommand")
            command = SetupCommand(
                subcommand,
                setup_config=options_get(Arguments.SETUP_CONFIG),
                dry_run=options_get(Arguments.DRY_RUN),
            )

        if command is not None:
            command.validate()
            return command
        else:
            raise Exception(
                "Please specify a command or run ./polaris --help to view the available commands"
            )

    def execute(self, api: PolarisDefaultApi) -> None:
        """
        Execute a given command and, where applicable, print the response as JSON.
        """
        raise Exception("`execute` called on abstract `Command`")

    def validate(self) -> None:
        """
        Used to validate a command. Should always be called before `execute`. The arg parser will catch many issues
        with options, but this is used to apply additional constraints that the arg parser can't currently handle.
        """
        raise Exception("`validate` called on abstract `Command`")
