# Copyright 2026 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import unittest
import json
import os
import re
import sys

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

sys.path.insert(0, REPOSITORY_ROOT)

import components.cronet.tools.breakages_constants as breakages_constants  # pylint: disable=wrong-import-position
import components.cronet.tools.utils as cronet_utils  # pylint: disable=wrong-import-position

FILE_PATH = os.path.join(REPOSITORY_ROOT, 'components', 'cronet', 'android',
                         'breakages.json')


class TestBreakagesJson(unittest.TestCase):

    parsed_breakages = []

    @classmethod
    def createFromBreakagesJson(cls):
        """Loads the JSON file once before tests run."""
        if not os.path.exists(FILE_PATH):
            raise FileNotFoundError(
                f"CRITICAL: The file '{FILE_PATH}' could not be found.")

        content = json.loads(cronet_utils.read_file(FILE_PATH))
        if "breakages" not in content:
            raise ValueError(
                "CRITICAL: Root of JSON must contain a 'breakages' key.")

        if not isinstance(content["breakages"], list):
            raise ValueError("CRITICAL: 'breakages' key must contain a list.")

        cls.parsed_breakages = content["breakages"]

    def _is_valid_change_id(self, change_id):
        if not isinstance(change_id, str):
            return False
        pattern = r'^I[0-9a-fA-F]{40}$'
        zeros_pattern = r'^I00*$'
        return bool(
            re.fullmatch(pattern, change_id)
            and not re.fullmatch(zeros_pattern, change_id))

    def _format_error(self, entry, message):
        entry_str = json.dumps(entry, indent=2)
        return f"\n{message}\n\nFailing Entry:\n{entry_str}\n"

    def test_id_validation_logic(self):
        self.assertTrue(self._is_valid_change_id("I" + "a" * 40))
        self.assertFalse(self._is_valid_change_id("I" + "a" * 39))  # Too short
        self.assertFalse(self._is_valid_change_id("a" * 40))  # Missing 'I'
        self.assertFalse(self._is_valid_change_id("I" + "z" * 40))  # Non-hex
        self.assertFalse(self._is_valid_change_id("I" + "0" * 40))  # All zeros

    def test_missing_mandatory_keys(self):
        required_keys = [
            breakages_constants.BUG_TXT, breakages_constants.BAD_CHANGE_ID_TXT,
            breakages_constants.GOOD_CHANGE_IDS_TXT,
            breakages_constants.COMMENT_TXT
        ]

        for entry in self.parsed_breakages:
            for key in required_keys:
                self.assertIn(
                    key, entry,
                    self._format_error(entry,
                                       f"Missing mandatory key: '{key}'"))

    def test_unknown_keys(self):
        allowed_keys = {
            breakages_constants.GOOD_CHANGE_IDS_TXT,
            breakages_constants.BAD_CHANGE_ID_TXT, breakages_constants.BUG_TXT,
            breakages_constants.COMMENT_TXT
        }

        for entry in self.parsed_breakages:
            for key in entry:
                if key.startswith('_'):
                    continue  # comments are allowed
                self.assertIn(
                    key, allowed_keys,
                    self._format_error(
                        entry,
                        f"Unknown key found: '{key}'. Allowed: {allowed_keys}")
                )

    def test_bad_change_id_format_in_file(self):
        """Test that 'bad_change_id' entries in the file follow the regex."""
        for entry in self.parsed_breakages:
            val = entry[breakages_constants.BAD_CHANGE_ID_TXT]
            self.assertTrue(
                self._is_valid_change_id(val),
                self._format_error(
                    entry,
                    f"Invalid '{breakages_constants.BAD_CHANGE_ID_TXT}' format: '{val}'"
                ))

    def test_good_change_ids_is_list(self):
        for entry in self.parsed_breakages:
            val = entry[breakages_constants.GOOD_CHANGE_IDS_TXT]
            self.assertIsInstance(
                val, list,
                self._format_error(
                    entry,
                    f"'{breakages_constants.GOOD_CHANGE_IDS_TXT}' must be a list. Found {type(val)}"
                ))

    def test_good_change_ids_format_in_file(self):
        for entry in self.parsed_breakages:
            for val in entry[breakages_constants.GOOD_CHANGE_IDS_TXT]:
                self.assertTrue(
                    self._is_valid_change_id(val),
                    self._format_error(
                        entry,
                        f"Invalid ID in '{breakages_constants.GOOD_CHANGE_IDS_TXT}': '{val}'"
                    ))


if __name__ == '__main__':
    unittest.main()
