#
# 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 functools
import json
import os
import sys
from json import JSONDecodeError
from typing import Optional, List, Any, cast

import urllib3

from apache_polaris.cli.api_client_builder import ApiClientBuilder
from apache_polaris.cli.constants import Commands
from apache_polaris.cli.options.parser import Parser
from apache_polaris.sdk.management import PolarisDefaultApi
from apache_polaris.sdk.management.exceptions import ApiException
from apache_polaris.cli.command.profiles import ProfilesCommand


class PolarisCli:
    """
    Implements a basic Command-Line Interface (CLI) for interacting with a Polaris service. The CLI can be used to
    manage entities like catalogs, principals, and grants within Polaris and can perform most operations that are
    available in the Python client API.

    Example usage:
    * ./polaris --client-id ${id} --client-secret ${secret} --host ${hostname} --port ${port} principals create example_user
    * ./polaris --client-id ${id} --client-secret ${secret} --host ${hostname} --port ${port} principal-roles create example_role
    * ./polaris --client-id ${id} --client-secret ${secret} --host ${hostname} --port ${port} catalog-roles list
    * ./polaris --client-id ${id} --client-secret ${secret} --base-url https://custom-polaris-domain.example.com/service-prefix catalogs list
    """

    # Can be enabled if the client is able to authenticate directly without first fetching a token
    DIRECT_AUTHENTICATION_ENABLED = False

    @staticmethod
    def execute(args: Optional[List[str]] = None) -> None:
        options = Parser.parse(args)
        if options.command == Commands.PROFILES:
            from apache_polaris.cli.command import Command

            command = Command.from_options(options)
            command = cast(ProfilesCommand, command)
            command.execute()
        else:
            api_client = ApiClientBuilder(
                options, direct_authentication=PolarisCli.DIRECT_AUTHENTICATION_ENABLED
            ).get_api_client()
            try:
                from apache_polaris.cli.command import Command

                admin_api = PolarisDefaultApi(api_client)
                command = Command.from_options(options)
                if options.debug:
                    PolarisCli._enable_api_request_logging()
                command.execute(admin_api)
            except ApiException as e:
                PolarisCli._try_print_exception(e)
                sys.exit(1)
            except Exception as e:
                sys.stderr.write(f"An unexpected error occurred: {e}{os.linesep}")
                sys.exit(1)

    @staticmethod
    def _enable_api_request_logging() -> None:
        # Store the original urlopen method
        if not hasattr(urllib3.PoolManager, "original_urlopen"):
            urllib3.PoolManager.original_urlopen = urllib3.PoolManager.urlopen

        # Define the wrapper function
        @functools.wraps(urllib3.PoolManager.original_urlopen)
        def urlopen_wrapper(
            self: urllib3.PoolManager, method: str, url: str, **kwargs: Any
        ) -> Any:
            sys.stderr.write(f"Request: {method} {url}\n")
            if "headers" in kwargs:
                sys.stderr.write(f"Headers: {kwargs['headers']}\n")
            if "body" in kwargs:
                sys.stderr.write(f"Body: {kwargs['body']}\n")
            sys.stderr.write("\n")
            # Call the original urlopen method
            return urllib3.PoolManager.original_urlopen(self, method, url, **kwargs)

        urllib3.PoolManager.urlopen = urlopen_wrapper

    @staticmethod
    def _try_print_exception(e: ApiException) -> None:
        try:
            error = json.loads(e.body)["error"]
            sys.stderr.write(
                f"Exception when communicating with the Polaris server."
                f" {error['type']}: {error['message']}{os.linesep}"
            )
        except JSONDecodeError as _:
            sys.stderr.write(
                f"Exception when communicating with the Polaris server."
                f" {e.status}: {e.reason}{os.linesep}"
            )
        except Exception as _:
            sys.stderr.write(
                f"Exception when communicating with the Polaris server. {e}{os.linesep}"
            )


def main() -> None:
    PolarisCli.execute()


if __name__ == "__main__":
    main()
