#!/usr/bin/env python
"""This tool builds or repacks the client binaries."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import getpass
import glob
import logging
import multiprocessing
import os
import platform
import subprocess
import sys

from absl import app
from absl import flags
from absl.flags import argparse_flags
import distro

from grr_response_client_builder import build_helpers
from grr_response_client_builder import builders
from grr_response_client_builder import repacking
from grr_response_core import config as grr_config
from grr_response_core.config import contexts
from grr_response_core.lib import config_lib
from grr_response_core.lib import utils

# pylint: disable=unused-import
# Required for google_config_validator
from grr_response_core.lib.local import plugins

# pylint: enable=unused-import

FLAGS = flags.FLAGS


class Error(Exception):
  pass


class ErrorDuringRepacking(Error):
  pass


# This is an absl flag, and not an argparse flag, for backwards compatibility.
flags.DEFINE_bool(
    "verbose",
    default=False,
    help="Turn on verbose logging.",
    allow_override=True)

parser = argparse_flags.ArgumentParser(
    description="A tool for building client binaries.")

# Initialize sub parsers and their arguments.
subparsers = parser.add_subparsers(
    title="subcommands", dest="subparser_name", description="valid subcommands")

# generate config
parser_generate_config = subparsers.add_parser(
    "generate_client_config", help="Generate client config.")

parser_generate_config.add_argument(
    "--client_config_output",
    help="Filename to write output.",
    required=True,
    default=None)

# build arguments.
parser_build = subparsers.add_parser(
    "build", help="Build a client from source.")

parser_build.add_argument(
    "--output", default=None, help="The path to write the output template.")

# repack arguments
parser_repack = subparsers.add_parser(
    "repack", help="Build installer from a template.")

parser_repack.add_argument(
    "--debug_build",
    action="store_true",
    default=False,
    help="Build a debug installer.")

parser_repack.add_argument(
    "--sign",
    action="store_true",
    default=False,
    help="Sign installer binaries.")

parser_repack.add_argument(
    "--signed_template",
    action="store_true",
    default=False,
    help="Set to true if template was signed with sign_template. This is only "
    "necessary when repacking a windows template many times.")

parser_repack.add_argument(
    "--template",
    default=None,
    required=True,
    help="The template zip file to repack.")

parser_repack.add_argument(
    "--output_dir",
    default="",
    required=True,
    help="The directory to which we should write the "
    "output installer. Installers will be named "
    "automatically from config options.")

# repack_multiple arguments
parser_multi = subparsers.add_parser(
    "repack_multiple", help="Repack multiple templates with multiple configs.")

parser_multi.add_argument(
    "--sign",
    action="store_true",
    default=False,
    help="Sign installer binaries.")

parser_multi.add_argument(
    "--signed_template",
    action="store_true",
    default=False,
    help="Set to true if template was signed with sign_template. This is only "
    "necessary when repacking a windows template many times.")

parser_multi.add_argument(
    "--templates",
    default=None,
    required=True,
    nargs="+",
    help="The list of templates to repack. Use "
    "'--template /some/dir/*.zip' to repack "
    "all templates in a directory.")

parser_multi.add_argument(
    "--repack_configs",
    default=None,
    required=True,
    nargs="+",
    help="The list of repacking configs to apply. Use "
    "'--repack_configs /some/dir/*.yaml' to repack "
    "with all configs in a directory")

parser_multi.add_argument(
    "--output_dir",
    default=None,
    required=True,
    help="The directory where we output our installers.")

parser_signer = subparsers.add_parser(
    "sign_template",
    help="Sign client libraries in a client template."
    "Use this when you are repacking a windows template many times and "
    "need all binaries inside signed.")

parser_signer.add_argument(
    "--template", default=None, required=True, help="Template to sign.")

parser_signer.add_argument(
    "--output_file",
    default=None,
    required=True,
    help="Where to write the new template with signed libs.")


class TemplateBuilder(object):
  """Build client templates."""

  def GetBuilder(self, context):
    """Get instance of builder class based on flags."""
    try:
      if "Target:Darwin" in context:
        return builders.DarwinClientBuilder(context=context)
      elif "Target:Windows" in context:
        return builders.WindowsClientBuilder(context=context)
      elif "Target:LinuxDeb" in context:
        return builders.DebianClientBuilder(context=context)
      elif "Target:LinuxRpm" in context:
        return builders.CentosClientBuilder(context=context)
      else:
        parser.error("Bad build context: %s" % context)
    except AttributeError:
      raise RuntimeError("Unable to build for platform %s when running "
                         "on current platform." % self.platform)

  def GetArch(self):
    if platform.architecture()[0] == "32bit":
      return "i386"
    return "amd64"

  def GetPackageFormat(self):
    if platform.system() == "Linux":
      distro_id = distro.id()
      if distro_id in ["ubuntu", "debian"]:
        return "Target:LinuxDeb"
      elif distro_id in ["centos", "rhel", "fedora"]:
        return "Target:LinuxRpm"
      else:
        message = "Unknown distro '{}', can't determine package format"
        raise RuntimeError(message.format(distro_id))

  def BuildTemplate(self, context=None, output=None):
    """Find template builder and call it."""
    context = context or []
    context.append("Arch:%s" % self.GetArch())
    # Platform context has common platform settings, Target has template build
    # specific stuff.
    self.platform = platform.system()
    context.append("Platform:%s" % self.platform)
    context.append("Target:%s" % self.platform)
    if "Target:Linux" in context:
      context.append(self.GetPackageFormat())

    template_path = None
    # If output is specified, place the built template file there, otherwise
    # use the default location prescribed by the config.
    if output:
      template_path = os.path.join(
          output,
          grr_config.CONFIG.Get(
              "PyInstaller.template_filename", context=context))
    else:
      template_path = grr_config.CONFIG.Get(
          "ClientBuilder.template_path", context=context)

    utils.EnsureDirExists(os.path.dirname(template_path))

    builder_obj = self.GetBuilder(context)
    builder_obj.MakeExecutableTemplate(template_path)


def SpawnProcess(popen_args, passwd=None):
  """Spawns a process."""
  if passwd is not None:
    # We send the password via pipe to avoid creating a process with the
    # password as an argument that will get logged on some systems.
    p = subprocess.Popen(popen_args, stdin=subprocess.PIPE)
    p.communicate(input=passwd)
  else: