# Copyright 2017 Google Inc. 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.

"""Tools for launching a browser."""

from __future__ import absolute_import
from __future__ import unicode_literals

import abc
import os
import subprocess
import sys
import webbrowser

from googlecloudsdk.command_lib.interactive import parser
from googlecloudsdk.core import log
from googlecloudsdk.core.util import files
import six


# TODO(b/35395811): Remove subprocess monkeypatch.
class FakeSubprocessModule(object):

  def Popen(self, args, **kwargs):
    with files.FileWriter(os.devnull) as devnull:
      kwargs.update({'stderr': devnull, 'stdout': devnull})
      return subprocess.Popen(args, **kwargs)


class CommandReferenceMapper(six.with_metaclass(abc.ABCMeta, object)):
  """Command to URL or man page reference mapper base class."""

  def __init__(self, cli, args):
    self.cli = cli
    self.args = args

  @abc.abstractmethod
  def GetMan(self):
    """Returns the man-style command for the command in args."""
    return None

  @abc.abstractmethod
  def GetURL(self):
    """Returns the help doc URL for the command in args."""
    return None


class GcloudReferenceMapper(CommandReferenceMapper):
  """gcloud help reference mapper."""

  def GetMan(self):
    return ' '.join(self.args + ['--help'])

  def GetURL(self):
    return '/'.join(
        ['https://cloud.google.com/sdk/gcloud/reference'] + self.args[1:])


class BqReferenceMapper(CommandReferenceMapper):
  """bq help reference mapper."""

  def GetMan(self):
    return self.args[0] + ' help | less'

  def GetURL(self):
    return 'https://cloud.google.com/bigquery/bq-command-line-tool'


class GsutilReferenceMapper(CommandReferenceMapper):
  """gsutil help reference mapper."""

  def __init__(self, cli, args):
    super(GsutilReferenceMapper, self).__init__(cli, args)
    self.subcommand = args[1] if len(args) > 1 else ''
    self.ref = ['https://cloud.google.com/storage/docs/gsutil']

  def GetMan(self):
    cmd = ['gsutil help']
    if self.subcommand:
      cmd.append(self.subcommand)
    cmd.append('| less')
    return ' '.join(cmd)

  def GetURL(self):
    if self.subcommand:
      self.ref.append('commands')
      self.ref.append(self.subcommand)
    return '/'.join(self.ref)


class KubectlReferenceMapper(CommandReferenceMapper):
  """kubectl help reference mapper."""

  def __init__(self, cli, args):
    super(KubectlReferenceMapper, self).__init__(cli, args)
    self.subcommand = args[1] if len(args) > 1 else ''
    try:
      full_version = (cli.root[parser.LOOKUP_COMMANDS][args[0]]
                      [parser.LOOKUP_CLI_VERSION])
      version = '.'.join(full_version.split('.')[0:2])
    except (IndexError, KeyError):
      version = 'v1.8'
    self.ref = ['https://kubernetes.io/docs/user-guide/kubectl', version]

  def GetMan(self):
    cmd = ['kubectl help']
    if self.subcommand:
      cmd.append(self.subcommand)
    cmd.append('| less')
    return ' '.join(cmd)

  def GetURL(self):
    if self.subcommand:
      self.ref.append('#' + self.subcommand)
    return '/'.join(self.ref)


class UnknownReferenceMapper(CommandReferenceMapper):
  """Unkmown command help reference mapper."""

  def __init__(self, cli, args):
    super(UnknownReferenceMapper, self).__init__(cli, args)
    self.known = files.FindExecutableOnPath(args[0])

  def GetMan(self):
    if not self.known:
      return None
    return 'man ' + self.args[0]

  def GetURL(self):
    if not self.known:
      return None
    if 'darwin' in sys.platform:
      ref = ['https://developer.apple.com/legacy/library/documentation',
             'Darwin/Reference/ManPages/man1']
    else:
      ref = ['http://man7.org/linux/man-pages/man1']
    ref.append(self.args[0] + '.1.html')
    return '/'.join(ref)


# TODO(b/35420203): get reference page for flag, not just command/group.
def _GetReferenceURL(cli, line, pos=None, man_page=False):
  """Determine the reference url of the command/group preceding the pos.

  Args:
    cli: the prompt CLI object
    line: a string with the current string directly from the shell.
    pos: the position of the cursor on the line.
    man_page: Return help/man page command line if True.

  Returns:
    A string containing the URL of the reference page.
  """
  mappers = {
      'bq': BqReferenceMapper,
      'gcloud': GcloudReferenceMapper,
      'gsutil': GsutilReferenceMapper,
      'kubectl': KubectlReferenceMapper,
  }

  if pos is None:
    pos = len(line)
  args = []
  for arg in cli.parser.ParseCommand(line):
    if arg.start < pos and (
        not args or
        arg.tree.get(parser.LOOKUP_COMMANDS) or  # IS_GROUP workaround
        arg.token_type in (parser.ArgTokenType.COMMAND,
                           parser.ArgTokenType.GROUP)):
      args.append(arg.value)
  if not args:
    if line:
      return None
    args = ['gcloud', 'alpha', 'interactive']

  mapper_class = mappers.get(args[0], UnknownReferenceMapper)
  mapper = mapper_class(cli, args)
  return mapper.GetMan() if man_page else mapper.GetURL()


def OpenReferencePage(cli, line, pos):
  """Opens a web browser or local help/man page for line at pos."""
  man_page = bool(os.environ.get('SSH_CLIENT'))
  ref = _GetReferenceURL(cli, line, pos, man_page)
  if not ref:
    return
  if man_page:
    cli.Run(ref, alternate_screen=True)
    return

  webbrowser.subprocess = FakeSubprocessModule()
  try:
    browser = webbrowser.get()
    browser.open_new_tab(ref)
  except webbrowser.Error as e:
    cli.run_in_terminal(
        lambda: log.error('failed to open browser: %s', e))
