# -*- coding: utf-8 -*- #
# Copyright 2017 Google LLC. All Rights Reserved.
#
# Licensed 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.

"""The meta cache completers run command."""


import sys

from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import parser_extensions
from googlecloudsdk.command_lib.meta import cache_util
from googlecloudsdk.command_lib.util import parameter_info_lib
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import module_util
from googlecloudsdk.core.console import console_io


class _FunctionCompleter:
  """Convert an argparse function completer to a resource_cache completer."""

  def __init__(self, completer):
    self._completer = completer
    self.parameters = None

  def ParameterInfo(self, parsed_args, argument):
    del argument
    return parsed_args

  def Complete(self, prefix, parameter_info):
    return self._completer(prefix, parsed_args=parameter_info)


def _GetPresentationSpec(resource_spec_path, **kwargs):
  """Build a presentation spec."""
  resource_spec = module_util.ImportModule(resource_spec_path)
  if callable(resource_spec):
    resource_spec = resource_spec()
  flag_name_overrides = kwargs.pop('flag_name_overrides', '')
  flag_name_overrides = {
      o.split(':')[0]: o.split(':')[1] if ':' in o else ''
      for o in flag_name_overrides.split(';')
      if o}
  prefixes = kwargs.pop('prefixes', False)
  return presentation_specs.ResourcePresentationSpec(
      kwargs.pop('name', resource_spec.name),
      resource_spec,
      'help text',
      flag_name_overrides=flag_name_overrides,
      prefixes=prefixes,
      **kwargs)


def _GetCompleter(module_path, cache=None, qualify=None,
                  resource_spec=None, presentation_kwargs=None, attribute=None,
                  **kwargs):
  """Returns an instantiated completer for module_path."""
  presentation_kwargs = presentation_kwargs or {}
  if resource_spec:
    presentation_spec = _GetPresentationSpec(resource_spec,
                                             **presentation_kwargs)
    completer = module_util.ImportModule(module_path)(
        presentation_spec.concept_spec,
        attribute)

  else:
    completer = module_util.ImportModule(module_path)
    if not isinstance(completer, type):
      return _FunctionCompleter(completer)
  try:
    return completer(
        cache=cache,
        qualified_parameter_names=qualify,
        **kwargs)
  except TypeError:
    return _FunctionCompleter(completer())


class AddCompleterResourceFlags(parser_extensions.DynamicPositionalAction):
  """Adds resource argument flags based on the completer."""

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self.__argument = None
    self.__completer = None

  def GenerateArgs(self, namespace, module_path):
    args = []
    presentation_kwargs = namespace.resource_presentation_kwargs or {}
    # Add the args that correspond to the resource arg, but make them
    # non-required.
    if namespace.resource_spec_path:
      spec = _GetPresentationSpec(namespace.resource_spec_path,
                                  **presentation_kwargs)
      info = concept_parsers.ConceptParser([spec]).GetInfo(spec.name)
      for arg in info.GetAttributeArgs():
        if arg.name.startswith('--'):
          arg.kwargs['required'] = False
        else:
          arg.kwargs['nargs'] = '?' if not spec.plural else '*'
        args.append(arg)
    kwargs = namespace.kwargs or {}
    self.__completer = _GetCompleter(
        module_path, qualify=namespace.qualify,
        resource_spec=namespace.resource_spec_path,
        presentation_kwargs=presentation_kwargs,
        attribute=namespace.attribute,
        **kwargs)
    if self.__completer.parameters:
      for parameter in self.__completer.parameters:
        dest = parameter_info_lib.GetDestFromParam(parameter.name)
        if hasattr(namespace, dest):
          # Don't add if its already been added.
          continue
        flag = parameter_info_lib.GetFlagFromDest(dest)
        arg = base.Argument(
            flag,
            dest=dest,
            category='RESOURCE COMPLETER',
            help=(
                f'{self.__completer.__class__.__name__} `{parameter.name}` '
                'parameter value.'))
        args.append(arg)
    self.__argument = base.Argument(
        'resource_to_complete',
        nargs='?',
        help=('The partial resource name to complete. Omit to enter an '
              'interactive loop that reads a partial resource name from the '
              'input and lists the possible prefix matches on the output '
              'or displays an ERROR message.'))
    args.append(self.__argument)
    return args

  def Completions(self, prefix, parsed_args, **kwargs):
    parameter_info = self.__completer.ParameterInfo(
        parsed_args, self.__argument)
    return self.__completer.Complete(prefix, parameter_info)


class Run(base.Command):
  """Cloud SDK completer module tester.

  *{command}* is an ideal way to debug completer modules without interference
  from the shell.  Shells typically ignore completer errors by disabling all
  standard output, standard error and exception messaging.  Specify
  `--verbosity=INFO` to enable completion and resource cache tracing.
  """

  @staticmethod
  def Args(parser):
    # Add a concept handler that will be stuffed dynamically with information.
    concept_parsers.ConceptParser([]).AddToParser(parser)
    parser.add_argument(
        '--resource-spec-path',
        help=('The resource spec path for a resource argument auto-generated '
              'completer.'))
    parser.add_argument(
        '--attribute',
        help=('The name of the resource attribute for a resource argument '
              'auto-generated completer.'))
    parser.add_argument(
        '--resource-presentation-kwargs',
        type=arg_parsers.ArgDict(
            spec={
                'name': str,
                'flag_name_overrides': str,
                'plural': bool,
                'prefixes': bool,
                'required': bool}),
        help=('Dict of kwargs to be passed to the presentation spec for the '
              'resource argument for which a completer is being tested, such '
              'as name, prefixes, plural, flag name overrides (format as a '
              'list of semicolon-separated key:value pairs). Prefixes is False '
              'by default. Name is the resource spec name by default.'))
    cache_util.AddCacheFlag(parser)
    parser.add_argument(
        '--qualify',
        metavar='NAME',
        type=arg_parsers.ArgList(),
        help=('A list of resource parameter names that must always be '
              'qualified. This is a manual setting for testing. The CLI sets '
              'this automatically.'))
    parser.add_argument(
        '--kwargs',
        metavar='NAME=VALUE',
        type=arg_parsers.ArgDict(),
        help=('Keyword arg dict passed to the completer constructor. For '
              'example, use this to set the resource collection and '
              'list command for `DeprecatedListCommandCompleter`:\n\n'
              '  --kwargs=collection=...,foo="..."'))
    parser.add_argument(
        '--stack-trace',
        action='store_true',
        default=True,
        help=('Enable all exception stack traces, including Cloud SDK core '
              'exceptions.'))
    parser.AddDynamicPositional(
        'module_path',
        action=AddCompleterResourceFlags,
        help=('The completer module path. Run $ gcloud meta completers list` '
              'to list the module paths of the available completers. A '
              'completer module may declare additional flags. Specify `--help` '
              'after _MODULE_PATH_ for details on the module specific flags.'
              '\n\nNOTE: To test resource argument completers, use the '
              'module path "googlecloudsdk.command_lib.util.completers:'
              'CompleterForAttribute". The flags `--resource-spec-path`, '
              '`--attribute`, and (if desired) `--resource-presentation-'
              'kwargs` must be provided BEFORE the positional. Unlike with '
              'most gcloud commands, the arguments are generated on the fly '
              'using the completer you provide, so all the information to '
              'create a resource completer needs to be provided up-front. For '
              'example:\n\n  $ {command} --resource-spec-path MODULE_PATH:'
              'SPEC_OBJECT --attribute ATTRIBUTE_NAME --resource-presentation-'
              'kwargs flag_name_overrides=ATTRIBUTE1:FLAG1;ATTRIBUTE2:FLAG2 '
              'googlecloudsdk.command_lib.util.completers:CompleterForAttribute'
             ))

  def Run(self, args):
    """Returns the results for one completion."""
    presentation_kwargs = args.resource_presentation_kwargs or {}
    with cache_util.GetCache(args.cache, create=True) as cache:
      log.info(f'cache name {cache.name}')
      if not args.kwargs:
        args.kwargs = {}
      # Create the ResourceInfo object that is used to hook up the parameter
      # info to the argparse namespace for resource argument completers.
      if args.resource_spec_path:
        spec = _GetPresentationSpec(
            args.resource_spec_path,
            **presentation_kwargs)
        spec.required = False
        resource_info = concept_parsers.ConceptParser([spec]).GetInfo(spec.name)
        # Since the argument being completed doesn't have the correct
        # dest, make sure the handler always gives the same ResourceInfo
        # object.
        def ResourceInfoMonkeyPatch(*args, **kwargs):
          del args, kwargs
          return resource_info
        args.CONCEPTS.ArgNameToConceptInfo = ResourceInfoMonkeyPatch

      completer = _GetCompleter(
          args.module_path, cache=cache, qualify=args.qualify,
          resource_spec=args.resource_spec_path,
          presentation_kwargs=presentation_kwargs,
          attribute=args.attribute,
          **args.kwargs)
      parameter_info = completer.ParameterInfo(
          args, args.GetPositionalArgument('resource_to_complete'))
      if args.resource_to_complete is not None:
        matches = completer.Complete(args.resource_to_complete, parameter_info)
        return [matches]
      while True:
        name = console_io.PromptResponse('COMPLETE> ')
        if name is None:
          break
        try:
          completions = completer.Complete(name, parameter_info)
        except (Exception, SystemExit) as e:  # pylint: disable=broad-except
          if args.stack_trace:
            exceptions.reraise(Exception(e))
          else:
            log.error(str(e))
          continue
        if completions:
          print('\n'.join(completions))
      sys.stderr.write('\n')
      return None
