# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Stage the Chromium checkout to update CTS test version."""

import contextlib
import json
import operator
import os
import re
import six
import sys
import tempfile
import threading
import urllib
import zipfile

sys.path.append(
    os.path.join(
        os.path.dirname(__file__), os.pardir, os.pardir, 'third_party',
        'catapult', 'devil'))
from devil.utils import cmd_helper

sys.path.append(
    os.path.join(
        os.path.dirname(__file__), os.pardir, os.pardir, 'third_party',
        'catapult', 'common', 'py_utils'))
from py_utils import tempfile_ext

SRC_DIR = os.path.abspath(
    os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))

TOOLS_DIR = os.path.join('android_webview', 'tools')

CONFIG_FILE = os.path.join('cts_config', 'webview_cts_gcs_path.json')
CONFIG_PATH = os.path.join(SRC_DIR, TOOLS_DIR, CONFIG_FILE)

CIPD_FILE = os.path.join('cts_archive', 'cipd.yaml')
CIPD_PATH = os.path.join(SRC_DIR, TOOLS_DIR, CIPD_FILE)

DEPS_FILE = 'DEPS'

TEST_SUITES_FILE = os.path.join('testing', 'buildbot', 'test_suites.pyl')

# Android desserts that are no longer receiving CTS updates at
# https://source.android.com/compatibility/cts/downloads
# Please update this list as more versions reach end-of-service.
END_OF_SERVICE_DESSERTS = ['L', 'M']

CTS_DEP_NAME = 'src/android_webview/tools/cts_archive'
CTS_DEP_PACKAGE = 'chromium/android_webview/tools/cts_archive'

CIPD_REFERRERS = [DEPS_FILE, TEST_SUITES_FILE]

_GENERATE_BUILDBOT_JSON = os.path.join('testing', 'buildbot',
                                       'generate_buildbot_json.py')

_ENSURE_FORMAT = """$ParanoidMode CheckIntegrity
@Subdir cipd
{} {}"""
_ENSURE_SUBDIR = 'cipd'

_RE_COMMENT_OR_BLANK = re.compile(r'^ *(#.*)?$')


class CTSConfig(object):
  """Represents a CTS config file."""

  def __init__(self, file_path=CONFIG_PATH):
    """Constructs a representation of the CTS config file.

    Only read operations are provided by this object.  Users should edit the
    file manually for any modifications.

    Args:
      file_path: Path to file.
    """
    self._path = os.path.abspath(file_path)
    with open(self._path) as f:
      self._config = json.load(f)

  def get_platforms(self):
    return sorted(self._config.keys())

  def get_archs(self, platform):
    return sorted(self._config[platform]['arch'].keys())

  def iter_platform_archs(self):
    for p in self.get_platforms():
      for a in self.get_archs(p):
        yield p, a

  def get_cipd_zip(self, platform, arch):
    return self._config[platform]['arch'][arch]['filename']

  def get_origin(self, platform, arch):
    return self._config[platform]['arch'][arch]['_origin']

  def get_origin_zip(self, platform, arch):
    return os.path.basename(self.get_origin(platform, arch))

  def get_apks(self, platform):
    return sorted([r['apk'] for r in self._config[platform]['test_runs']])


class CTSCIPDYaml(object):
  """Represents a CTS CIPD yaml file."""

  RE_PACKAGE = r'^package:\s*(\S+)\s*$'
  RE_DESC = r'^description:\s*(.+)$'
  RE_DATA = r'^data:\s*$'
  RE_FILE = r'^\s+-\s+file:\s*(.+)$'

  # TODO(crbug.com/1049432): Replace with yaml parser
  @classmethod
  def parse(cls, lines):
    result = {}
    for line in lines:
      if len(line) == 0 or line[0] == '#':
        continue
      package_match = re.match(cls.RE_PACKAGE, line)
      if package_match:
        result['package'] = package_match.group(1)
        continue
      desc_match = re.match(cls.RE_DESC, line)
      if desc_match:
        result['description'] = desc_match.group(1)
        continue
      if re.match(cls.RE_DATA, line):
        result['data'] = []
      if 'data' in result:
        file_match = re.match(cls.RE_FILE, line)
        if file_match:
          result['data'].append({'file': file_match.group(1)})
    return result

  def __init__(self, file_path=CIPD_PATH):
    """Constructs a representation of CTS CIPD yaml file.

    Note the file won't be modified unless write is called
    with its path.
    Args:
      file_path: Path to file.
    """
    self._path = os.path.abspath(file_path)
    self._header = []
    # Read header comments
    with open(self._path) as f:
      for l in f.readlines():
        if re.match(_RE_COMMENT_OR_BLANK, l):
          self._header.append(l)
        else:
          break
    # Read yaml data
    with open(self._path) as f:
      self._yaml = CTSCIPDYaml.parse(f.readlines())

  def get_file_path(self):
    """Get full file path of yaml file that this was constructed from."""
    return self._path

  def get_file_basename(self):
    """Get base file name that this was constructed from."""
    return os.path.basename(self._path)

  def get_package(self):
    """Get package name."""
    return self._yaml['package']

  def clear_files(self):
    """Clears all files in file (only in local memory, does not modify file)."""
    self._yaml['data'] = []

  def append_file(self, file_name):
    """Add file_name to list of files."""
    self._yaml['data'].append({'file': str(file_name)})

  def remove_file(self, file_name):
    """Remove file_name from list of files."""
    old_file_names = self.get_files()
    new_file_names = [name for name in old_file_names if name != file_name]
    self._yaml['data'] = [{'file': name} for name in new_file_names]

  def get_files(self):
    """Get list of files in yaml file."""
    return [e['file'] for e in self._yaml['data']]

  def write(self, file_path):
    """(Over)write file_path with the cipd.yaml representation."""
    dir_name = os.path.dirname(file_path)
    if not os.path.isdir(dir_name):
      os.makedirs(dir_name)
    with open(file_path, 'w') as f:
      f.writelines(self._get_yamls())

  def _get_yamls(self):
    """Return the cipd.yaml file contents of this object."""
    output = []
    output += self._header
    output.append('package: {}\n'.format(self._yaml['package']))
    output.append('description: {}\n'.format(self._yaml['description']))
    output.append('data:\n')
    for d in sorted(self._yaml['data'], key=operator.itemgetter('file')):
      output.append('  - file: {}\n'.format(d.get('file')))
    return output


def cipd_ensure(package, version, root_dir):
  """Ensures CIPD package is installed at root_dir.

  Args: