#!/usr/bin/env python3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Checks that the histograms at given xml use variants defined in the file."""

import logging
import os
import sys
import xml.dom.minidom

import setup_modules  # pylint: disable=unused-import

from chromium_src.tools.metrics.common import path_util
import chromium_src.tools.metrics.common.xml_utils as xml_utils


def _LoadGlobalVariants() -> list[str]:
  """Loads the global variants from `variants.xml`, if it exists."""
  global_variants_list: list[str] = []
  global_variants_path: str = str(
    path_util.METRICS_TOOLS_PATH / 'histograms' / 'variants.xml'
  )
  if os.path.exists(global_variants_path):
    global_tree: xml.dom.minidom.Document = xml.dom.minidom.parse(
      global_variants_path
    )
    for node in xml_utils.IterElementsWithTag(global_tree, 'variants', 3):
      global_variants_name: str = node.getAttribute('name')
      global_variants_list.append(global_variants_name)
  return global_variants_list


def ValidateTokenInFile(xml_path: str) -> bool:
  """Validates that all <token> uses <variants> defined in the file.

  Args:
    xml_path: The path to the histograms.xml file.

  Returns:
    A boolean that is True if at least a histogram uses a <variants> not
    defined in the file or in the global variants.xml file, False otherwise.
  """
  has_token_error: bool = False
  tree: xml.dom.minidom.Document = xml.dom.minidom.parse(xml_path)
  variants: list[str] = []

  for node in xml_utils.IterElementsWithTag(tree, 'variants', 3):
    variants_name: str = node.getAttribute('name')
    variants.append(variants_name)

  # `global_variants` is loaded lazily when needed - if variant tokens are not
  # found in the histogram file where they are being used.
  global_variants: list[str] | None = None
  for histogram in xml_utils.IterElementsWithTag(tree, 'histogram', 3):
    erroneous_tokens: list[str] = []
    for node in xml_utils.IterElementsWithTag(histogram, 'token', 1):
      if node.hasAttribute('variants'):
        node_variant: str = node.getAttribute('variants')
        if node_variant not in variants:
          if global_variants is None:
            global_variants = _LoadGlobalVariants()
          if node_variant not in global_variants:
            erroneous_tokens.append(node.getAttribute('key'))
    if erroneous_tokens:
      histogram_name: str = histogram.getAttribute('name')
      logging.error(
        'Token(s) %s in histogram %s are using variants not defined in the '
        'file, please define them before use.',
        ', '.join(erroneous_tokens),
        histogram_name,
      )
      has_token_error = True

  return has_token_error


def main() -> None:
  """Checks that the histograms at given path use variants defined in the file.

  Args:
    sys.argv[1]: The relative path to xml file.

  Example usage:
    validate_token.py metadata/Fingerprint/histograms.xml
  """
  if len(sys.argv) != 2:
    sys.stderr.write('Usage: %s <rel-path-to-xml>' % sys.argv[0])
    sys.exit(1)

  xml_path: str = os.path.join(os.getcwd(), sys.argv[1])
  token_error: bool = ValidateTokenInFile(xml_path)

  sys.exit(token_error)


if __name__ == '__main__':
  main()
