#!/usr/bin/env python3
# Copyright (C) 2021 The Android Open Source Project
#
# 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.

# This script runs the Playwright tests for the UI. It can be run from the root
# directory with `./ui/run-integrationtests` and accepts the same arguments as
# `playwright test` (e.g. `./ui/run-integrationtests --workers=1
# src/test/wattson.test.ts`).
#
# It makes sure that the build is up to date before 

import argparse
import os
import shlex
import subprocess
import sys

UI_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(UI_DIR)

# Custom Playwright Docker image name (built from ui/playwright/Dockerfile)
PLAYWRIGHT_IMAGE = 'perfetto-playwright'
PLAYWRIGHT_DOCKERFILE = os.path.join(UI_DIR, 'playwright', 'Dockerfile')


def get_playwright_version():
  """Get the installed Playwright version."""
  result = subprocess.run(
      ['./pnpm', 'exec', 'playwright', '--version'],
      capture_output=True,
      text=True,
      cwd=UI_DIR
  )
  if result.returncode != 0:
    raise RuntimeError(f'Failed to get Playwright version: {result.stderr}')
  version_line = result.stdout.strip()
  if not version_line.startswith('Version '):
    raise RuntimeError(f'Unexpected Playwright version output: {version_line}')
  return version_line[len('Version '):]


def build_docker_image(version, use_sudo):
  """Build the custom Playwright Docker image with Mesa GL support."""
  image_tag = f'{PLAYWRIGHT_IMAGE}:v{version}'

  print(f'Building Docker image: {image_tag}')
  build_cmd = [
      'docker', 'build',
      '--build-arg', f'PLAYWRIGHT_VERSION={version}',
      '-t', image_tag,
      os.path.dirname(PLAYWRIGHT_DOCKERFILE)
  ]
  if use_sudo:
    build_cmd = ['sudo'] + build_cmd

  result = subprocess.run(build_cmd)
  if result.returncode != 0:
    raise RuntimeError(f'Failed to build Docker image')

  return image_tag


def is_docker_available():
  """Check if docker is installed and accessible."""
  try:
    result = subprocess.run(
        ['docker', '--version'],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        timeout=5
    )
    return result.returncode == 0
  except (subprocess.TimeoutExpired, FileNotFoundError):
    return False


def needs_sudo_for_docker():
  """Check if sudo is needed to run docker commands."""
  try:
    result = subprocess.run(
        ['docker', 'info'],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
        timeout=5
    )
    return result.returncode != 0
  except (subprocess.TimeoutExpired, FileNotFoundError):
    return True


def run_build(args):
  """Run the build script to ensure we have something to test."""
  build_args = []
  if args.no_build:
    build_args += ['--no-build']
  if args.no_depscheck:
    build_args += ['--no-depscheck']

  return subprocess.call(['ui/build'] + build_args)


def run_native(args):
  """Run tests directly on the host machine."""
  print('Warning: Running without Docker. Results may vary across environments.')
  if args.rebaseline:
    print('Note: Rebaselining outside Docker may cause snapshot mismatches in CI.')
  print('')

  # Install the chromium through playwright
  subprocess.check_call(['./pnpm', 'exec', 'playwright', 'install', 'chromium'], cwd=UI_DIR)

  cmd = ['./pnpm', 'exec', 'playwright', 'test']
  if args.interactive:
    if args.rebaseline:
      print('--interactive and --rebaseline are mutually exclusive')
      return 1
    cmd += ['--ui']
  elif args.rebaseline:
    cmd += ['--update-snapshots']

  if args.workers:
    cmd += ['--workers', args.workers]

  cmd += args.filters

  env = dict(os.environ.items())
  dev_server_args = []
  if args.out:
    out_rel_path = os.path.relpath(args.out, UI_DIR)
    env['OUT_DIR'] = out_rel_path
    dev_server_args += ['--out', out_rel_path]
  env['DEV_SERVER_ARGS'] = ' '.join(dev_server_args)
  os.chdir(UI_DIR)
  os.execve(cmd[0], cmd, env)


def run_in_docker(args):
  """Run tests inside a Docker container with pre-installed Chrome."""
  use_sudo = needs_sudo_for_docker()

  # Get the Playwright version and build/use matching Docker image
  try:
    version = get_playwright_version()
    image = build_docker_image(version, use_sudo)
  except RuntimeError as e:
    print(str(e))
    return 1

  # Build the playwright command (wrapped with xvfb-run for virtual display)
  # First install browsers to /tmp (the only writable location in the container)
  install_cmd = 'PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright ./pnpm exec playwright install chromium'
  playwright_cmd_parts = [
      'unbuffer', 'xvfb-run', '--auto-servernum',
      './pnpm', 'exec', 'playwright', 'test'
  ]
  if args.rebaseline:
    playwright_cmd_parts += ['--update-snapshots']
  if args.workers:
    playwright_cmd_parts += ['--workers', args.workers]
  playwright_cmd_parts += args.filters

  playwright_test_cmd = ' '.join(shlex.quote(p) for p in playwright_cmd_parts)
  playwright_cmd = f'{install_cmd} && PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright {playwright_test_cmd}'

  # Build dev server args (similar to run_native)
  dev_server_args = []
  env_args = []
  if args.out:
    out_rel_path = os.path.relpath(args.out, UI_DIR)
    env_args += ['-e', f'OUT_DIR={out_rel_path}']
    dev_server_args += ['--out', out_rel_path]
  if dev_server_args:
    env_args += ['-e', f'DEV_SERVER_ARGS={" ".join(dev_server_args)}']

  docker_cmd = [
      'docker', 'run',
      '--rm', # Remove the container after it exits
      '-t', # Allocate a pseudo-TTY for better output formatting
      '-v', f'{REPO_ROOT}:{REPO_ROOT}', # Mount the repo root into the container
      '-w', f'{REPO_ROOT}/ui', # Set working directory to /ui
      '-u', f'{os.getuid()}:{os.getgid()}', # Run as current user to avoid permission issues
      '-e', 'HOME=/tmp', # Chrome needs a writable home for crashpad
      *env_args,
      image,
      'bash', '-c', playwright_cmd
  ]

  if use_sudo:
    docker_cmd = ['sudo'] + docker_cmd

  print(f'Running Playwright in Docker using {image}')
  if docker_cmd[0] == 'sudo':
    print('Note: you may be prompted for your password to run Docker with sudo.')
  sys.exit(subprocess.call(docker_cmd))


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument(
      '--no-docker',
      action='store_true',
      help='Run tests on the host machine instead of in Docker')
  parser.add_argument(
      '--interactive',
      '-i',
      action='store_true',
      help='Run in interactive mode (requires --no-docker)')
  parser.add_argument(
      '--rebaseline', '-r', action='store_true', help='Rebaseline screenshots')
  parser.add_argument('--out', help='out directory')
  parser.add_argument('--no-build', action='store_true')
  parser.add_argument('--no-depscheck', action='store_true')
  parser.add_argument('--workers', help='Number of playwright workers to use')
  parser.add_argument('filters', nargs='*')
  args = parser.parse_args()

  if args.interactive and not args.no_docker:
    print('--interactive requires --no-docker (Docker has no display)')
    print('Try: ./run-integrationtests --no-docker --interactive')
    return 1

  run_build_result = run_build(args)
  if run_build_result != 0:
    print('Build failed, not running tests')
    return run_build_result

  if args.no_docker:
    return run_native(args)

  if not is_docker_available():
    print('Warning: Docker is not installed or not accessible.')
    print('Docker is recommended for consistent test results, especially when')
    print('rebaselining screenshots.')
    print('')
    print('To run without Docker, pass --no-docker:')
    print('  ./run-integrationtests --no-docker')
    print('')
    return 1

  return run_in_docker(args)


if __name__ == '__main__':
  sys.exit(main())
