#
# 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 dataclasses import dataclass, field
from typing import List

from cli.constants import StorageType, CatalogType, PrincipalType, Hints, Commands, Arguments, Subcommands, Actions


@dataclass
class Argument:
    """
    A data class for representing a single argument within the CLI, such as `--host`.
    """

    name: str
    type: type
    hint: str
    choices: List[str] = None
    lower: bool = False
    allow_repeats: bool = False
    default: object = None

    def __post_init__(self):
        if self.name.startswith('--'):
            raise Exception(f'Argument name {self.name} starts with `--`: should this be a flag_name?')

    @staticmethod
    def to_flag_name(argument_name):
        return '--' + argument_name.replace('_', '-')

    def get_flag_name(self):
        return Argument.to_flag_name(self.name)



@dataclass
class Option:
    """
    A data class that represents a subcommand within the CLI, such as `catalogs`. Each Option can have child Options,
    a collection of Arguments, or both.
    """

    name: str
    hint: str = None
    input_name: str = None
    args: List[Argument] = field(default_factory=list)
    children: List['Option'] = field(default_factory=list)


class OptionTree:
    """
    `OptionTree.get_tree()` returns the full set of Options supported by the CLI. This structure is used to simplify
    configuration of the CLI and to generate a custom `--help` message including nested commands.
    """

    _CATALOG_ROLE_AND_CATALOG = [
        Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
        Argument(Arguments.CATALOG_ROLE, str, Hints.CatalogRoles.CATALOG_ROLE)
    ]

    @staticmethod
    def get_tree() -> List[Option]:
        return [
            Option(Commands.CATALOGS, 'manage catalogs', children=[
                Option(Subcommands.CREATE, args=[
                    Argument(Arguments.TYPE, str, Hints.Catalogs.Create.TYPE, lower=True,
                             choices=[ct.value for ct in CatalogType], default=CatalogType.INTERNAL.value),
                    Argument(Arguments.STORAGE_TYPE, str, Hints.Catalogs.Create.STORAGE_TYPE, lower=True,
                             choices=[st.value for st in StorageType]),
                    Argument(Arguments.DEFAULT_BASE_LOCATION, str, Hints.Catalogs.Create.DEFAULT_BASE_LOCATION),
                    Argument(Arguments.ALLOWED_LOCATION, str, Hints.Catalogs.Create.ALLOWED_LOCATION,
                             allow_repeats=True),
                    Argument(Arguments.ROLE_ARN, str, Hints.Catalogs.Create.ROLE_ARN),
                    Argument(Arguments.REGION, str, Hints.Catalogs.Create.REGION),
                    Argument(Arguments.EXTERNAL_ID, str, Hints.Catalogs.Create.EXTERNAL_ID),
                    Argument(Arguments.TENANT_ID, str, Hints.Catalogs.Create.TENANT_ID),
                    Argument(Arguments.MULTI_TENANT_APP_NAME, str, Hints.Catalogs.Create.MULTI_TENANT_APP_NAME),
                    Argument(Arguments.CONSENT_URL, str, Hints.Catalogs.Create.CONSENT_URL),
                    Argument(Arguments.SERVICE_ACCOUNT, str, Hints.Catalogs.Create.SERVICE_ACCOUNT),
                    Argument(Arguments.REMOTE_URL, str, Hints.Catalogs.Create.REMOTE_URL),
                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, allow_repeats=True),
                ], input_name=Arguments.CATALOG),
                Option(Subcommands.DELETE, input_name=Arguments.CATALOG),
                Option(Subcommands.GET, input_name=Arguments.CATALOG),
                Option(Subcommands.LIST, args=[
                    Argument(Arguments.PRINCIPAL_ROLE, str, Hints.PrincipalRoles.PRINCIPAL_ROLE)
                ]),
                Option(Subcommands.UPDATE, args=[
                    Argument(Arguments.DEFAULT_BASE_LOCATION, str, Hints.Catalogs.Update.DEFAULT_BASE_LOCATION),
                    Argument(Arguments.ALLOWED_LOCATION, str, Hints.Catalogs.Create.ALLOWED_LOCATION,
                             allow_repeats=True),
                    Argument(Arguments.REGION, str, Hints.Catalogs.Create.REGION),
                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, allow_repeats=True),
                    Argument(Arguments.REMOVE_PROPERTY, str, Hints.REMOVE_PROPERTY, allow_repeats=True),
                ], input_name=Arguments.CATALOG)
            ]),
            Option(Commands.PRINCIPALS, 'manage principals', children=[
                Option(Subcommands.CREATE, args=[
                    Argument(Arguments.TYPE, str, Hints.Principals.Create.TYPE, lower=True,
                             choices=[pt.value for pt in PrincipalType], default=PrincipalType.SERVICE.value),
                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, allow_repeats=True)
                ], input_name=Arguments.PRINCIPAL),
                Option(Subcommands.DELETE, input_name=Arguments.PRINCIPAL),
                Option(Subcommands.GET, input_name=Arguments.PRINCIPAL),
                Option(Subcommands.LIST),
                Option(Subcommands.ROTATE_CREDENTIALS, input_name=Arguments.PRINCIPAL),
                Option(Subcommands.UPDATE, args=[
                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, allow_repeats=True),
                    Argument(Arguments.REMOVE_PROPERTY, str, Hints.REMOVE_PROPERTY, allow_repeats=True),
                ], input_name=Arguments.PRINCIPAL),
                Option(Subcommands.ACCESS, input_name=Arguments.PRINCIPAL),
            ]),
            Option(Commands.PRINCIPAL_ROLES, 'manage principal roles', children=[
                Option(Subcommands.CREATE, args=[
                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, allow_repeats=True)
                ], input_name=Arguments.PRINCIPAL_ROLE),
                Option(Subcommands.DELETE, input_name=Arguments.PRINCIPAL_ROLE),
                Option(Subcommands.GET, input_name=Arguments.PRINCIPAL_ROLE),
                Option(Subcommands.LIST, hint=Hints.PrincipalRoles.LIST, args=[
                    Argument(Arguments.CATALOG_ROLE, str, Hints.PrincipalRoles.List.CATALOG_ROLE),
                    Argument(Arguments.PRINCIPAL, str, Hints.PrincipalRoles.List.PRINCIPAL_NAME)
                ]),
                Option(Subcommands.UPDATE, args=[
                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, allow_repeats=True),
                    Argument(Arguments.REMOVE_PROPERTY, str, Hints.REMOVE_PROPERTY, allow_repeats=True),
                ], input_name=Arguments.PRINCIPAL_ROLE),
                Option(Subcommands.GRANT, hint=Hints.PrincipalRoles.GRANT, args=[
                    Argument(Arguments.PRINCIPAL, str, Hints.PrincipalRoles.Grant.PRINCIPAL)
                ], input_name=Arguments.PRINCIPAL_ROLE),
                Option(Subcommands.REVOKE, hint=Hints.PrincipalRoles.REVOKE, args=[
                    Argument(Arguments.PRINCIPAL, str, Hints.PrincipalRoles.Revoke.PRINCIPAL)
                ], input_name=Arguments.PRINCIPAL_ROLE)
            ]),
            Option(Commands.CATALOG_ROLES, 'manage catalog roles', children=[
                Option(Subcommands.CREATE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, allow_repeats=True)
                ], input_name=Arguments.CATALOG_ROLE),
                Option(Subcommands.DELETE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                ], input_name=Arguments.CATALOG_ROLE),
                Option(Subcommands.GET, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                ], input_name=Arguments.CATALOG_ROLE),
                Option(Subcommands.LIST, hint=Hints.CatalogRoles.LIST, args=[
                    Argument(Arguments.PRINCIPAL_ROLE, str, Hints.PrincipalRoles.PRINCIPAL_ROLE)
                ], input_name=Arguments.CATALOG),
                Option(Subcommands.UPDATE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                    Argument(Arguments.SET_PROPERTY, str, Hints.SET_PROPERTY, allow_repeats=True),
                    Argument(Arguments.REMOVE_PROPERTY, str, Hints.REMOVE_PROPERTY, allow_repeats=True),
                ], input_name=Arguments.CATALOG_ROLE),
                Option(Subcommands.GRANT, hint=Hints.CatalogRoles.GRANT_CATALOG_ROLE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                    Argument(Arguments.PRINCIPAL_ROLE, str, Hints.CatalogRoles.CATALOG_ROLE)
                ], input_name=Arguments.CATALOG_ROLE),
                Option(Subcommands.REVOKE, hint=Hints.CatalogRoles.GRANT_CATALOG_ROLE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                    Argument(Arguments.PRINCIPAL_ROLE, str, Hints.CatalogRoles.CATALOG_ROLE)
                ], input_name=Arguments.CATALOG_ROLE)
            ]),
            Option(Commands.PRIVILEGES, 'manage privileges for a catalog role', children=[
                Option(Subcommands.LIST, args=OptionTree._CATALOG_ROLE_AND_CATALOG),
                Option(Subcommands.CATALOG, children=[
                    Option(Actions.GRANT, args=OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                    Option(Actions.REVOKE, args=[
                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                ]),
                Option(Subcommands.NAMESPACE, children=[
                    Option(Actions.GRANT, args=[
                        Argument(Arguments.NAMESPACE, str, Hints.Grant.NAMESPACE)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                    Option(Actions.REVOKE, args=[
                        Argument(Arguments.NAMESPACE, str, Hints.Grant.NAMESPACE),
                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                ]),
                Option(Subcommands.TABLE, children=[
                    Option(Actions.GRANT, args=[
                        Argument(Arguments.NAMESPACE, str, Hints.Grant.NAMESPACE),
                        Argument(Arguments.TABLE, str, Hints.Grant.TABLE)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                    Option(Actions.REVOKE, args=[
                        Argument(Arguments.NAMESPACE, str, Hints.Grant.NAMESPACE),
                        Argument(Arguments.TABLE, str, Hints.Grant.TABLE),
                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                ]),
                Option(Subcommands.VIEW, children=[
                    Option(Actions.GRANT, args=[
                        Argument(Arguments.NAMESPACE, str, Hints.Grant.NAMESPACE),
                        Argument(Arguments.VIEW, str, Hints.Grant.VIEW)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                    Option(Actions.REVOKE, args=[
                        Argument(Arguments.NAMESPACE, str, Hints.Grant.NAMESPACE),
                        Argument(Arguments.VIEW, str, Hints.Grant.VIEW),
                        Argument(Arguments.CASCADE, bool, Hints.Grant.CASCADE)
                    ] + OptionTree._CATALOG_ROLE_AND_CATALOG, input_name=Arguments.PRIVILEGE),
                ])
            ]),
            Option(Commands.NAMESPACES, 'manage namespaces', children=[
                Option(Subcommands.CREATE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                    Argument(Arguments.LOCATION, str, Hints.Namespaces.LOCATION),
                    Argument(Arguments.PROPERTY, str, Hints.PROPERTY, allow_repeats=True)
                ], input_name=Arguments.NAMESPACE),
                Option(Subcommands.LIST, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME),
                    Argument(Arguments.PARENT, str, Hints.Namespaces.PARENT)
                ]),
                Option(Subcommands.DELETE, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME)
                ], input_name=Arguments.NAMESPACE),
                Option(Subcommands.GET, args=[
                    Argument(Arguments.CATALOG, str, Hints.CatalogRoles.CATALOG_NAME)
                ], input_name=Arguments.NAMESPACE),
            ]),
            Option(Commands.PROFILES, 'manage profiles', children=[
                Option(Subcommands.CREATE, input_name=Arguments.PROFILE),
                Option(Subcommands.DELETE, input_name=Arguments.PROFILE),
                Option(Subcommands.UPDATE, input_name=Arguments.PROFILE),
                Option(Subcommands.GET, input_name=Arguments.PROFILE),
                Option(Subcommands.LIST),
            ])
        ]
