#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
# SPDX-FileCopyrightText: 2023 Olivier Dion <odion@efficios.com>
# SPDX-FileCopyrightText: 2026 Kienan Stewart <kstewart@efficios.com>
#

#    shellcheck --external-sources "$1"
import logging
import os
import pathlib
import re
from typing import List, Set
import sys
import shutil
import subprocess

# Import in-tree test utils
# Modify this depending on where the test file is in the tree
test_utils_import_path = pathlib.Path(__file__).absolute().parents[1] / "utils"
sys.path.insert(0, str(test_utils_import_path))

import lttngtest

shell_file_re = re.compile(r"\.(ba)?sh")
shebang_re = re.compile(r"^#!.*sh$")


def has_shell_shebang(path: pathlib.Path) -> bool:
    head = ""
    try:
        with open(path, "r") as f:
            head = f.readline()
    except UnicodeDecodeError:
        return False

    return shebang_re.match(head) is not None


def identify_shell_files(
    base_path: pathlib.Path, directories: List, exclusions: List = list()
) -> Set[pathlib.Path]:
    files = set()
    for directory in directories:
        # pathlib.Path walk() is Python 3.12+
        for _directory, dirnames, filenames in os.walk(base_path / directory):
            for filename in filenames:
                candidate = pathlib.Path(_directory) / filename
                if filename.endswith(".in"):
                    logging.debug(
                        "'{}' matches input template pattern, skipping".format(
                            candidate
                        )
                    )
                    continue

                if str(candidate.relative_to(base_path)) in exclusions:
                    logging.debug("'{}' is in exlusion list".format(candidate))
                    continue

                if shell_file_re.match(str(filename)):
                    files.add(candidate)
                    continue

                # Check shebang
                if has_shell_shebang(candidate):
                    p = subprocess.Popen(
                        ["grep", "-q", "# Generated by libtool", str(candidate)]
                    )
                    p.wait()
                    if p.returncode == 0:
                        logging.debug(
                            "'{}' appears to be generated by libtool, skipping".format(
                                candidate
                            )
                        )
                        continue

                    files.add(candidate)

    return files


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format=lttngtest.utils.get_logging_format())
    project_root = pathlib.Path(__file__).absolute().parents[2]

    # Exclusions relative to project root
    exclusions = [
        "tests/perf/test_perf_raw",
        "tests/utils/testapp/userspace-probe-elf-binary",
        "tests/regression/tools/trigger/utils/register-some-triggers",
        "tests/regression/tools/tracefile-limits/test_tracefile_count",
        "tests/regression/tools/crash/test_crash",
        "tests/utils/warn_processes.sh",
        "tests/regression/ust/linking/demo_preload",
        "tests/regression/tools/snapshots/test_kernel_streaming",
        "tests/regression/tools/snapshots/ust_test",
        "tests/regression/tools/regen-metadata/test_kernel",
        "tests/regression/ust/java-log4j/test_java_log4j",
        "tests/regression/tools/notification/test_notification_kernel_error",
        "tests/regression/kernel/test_kernel_function",
        "tests/regression/tools/regen-statedump/test_kernel",
        "tests/utils/tap-driver.sh",
        "tests/regression/tools/rotation/test_schedule_api",
        "tests/regression/tools/notification/test_notification_ust_event_rule_condition_exclusion",
        "tests/utils/testapp/gen-data-pending",
        "tests/stress/test_multi_sessions_per_uid_5app_streaming",
        "tests/regression/kernel/test_rotation_destroy_flush",
        "tests/unit/test_condition",
        "tests/regression/ust/java-jul/test_java_jul",
        "tests/regression/ust/java-log4j2/test_agent_log4j2_domain_log4j",
        "tests/regression/tools/save-load/test_save",
        "tests/unit/test_rate_policy",
        "tests/regression/kernel/test_syscall",
        "tests/regression/tools/notification/test_notification_ust_error",
        "tests/regression/tools/streaming/test_kernel",
        "tests/regression/kernel/test_ns_contexts",
        "tests/regression/tools/trigger/hidden/hidden_trigger",
        "tests/regression/tools/rotation/test_save_load_mi",
        "tests/regression/tools/trigger/test_remove_trigger_cli",
        "tests/regression/kernel/test_event_basic",
        "tests/stress/kill_relayd",
        "tests/utils/testapp/userspace-probe-sdt-binary",
        "tests/unit/test_kernel_probe",
        "tests/regression/tools/metadata/test_ust",
        "tests/regression/tools/live/test_kernel",
        "tests/regression/tools/notification/test_rotation",
        "tests/regression/tools/snapshots/test_ust_fast",
        "tests/regression/tools/rotation/test_ust",
        "tests/unit/test_notification",
        "tests/regression/tools/streaming/test_ust",
        "tests/regression/tools/notification/base_client",
        "tests/unit/test_event_expr_to_bytecode",
        "tests/destructive/metadata-regeneration",
        "tests/regression/kernel/test_lttng_logger",
        "tests/regression/kernel/test_userspace_probe",
        "tests/stress/launch_ust_app",
        "tests/regression/ust/java-log4j2/test_agent_log4j2_domain_log4j2",
        "tests/regression/tools/working-directory/test_relayd_working_directory",
        "tests/regression/tools/live/test_lttng_kernel",
        "tests/regression/tools/health/test_thread_stall",
        "tests/regression/tools/tracker/test_event_tracker",
        "tests/unit/test_action_key_template",
        "tests/utils/testapp/gen-ust-events-constructor/gen-ust-events-constructor-so",
        "tests/regression/tools/trigger/test_add_trigger_cli",
        "tests/regression/tools/metadata/test_kernel",
        "tests/regression/kernel/test_channel",
        "tests/regression/tools/health/test_tp_fail",
        "tests/unit/test_log_level_rule",
        "tests/regression/kernel/test_ns_contexts_change",
        "tests/regression/ust/python-logging/test_python_logging",
        "tests/regression/tools/health/health_check",
        "tests/regression/tools/notification/test_notification_kernel_syscall",
        "tests/regression/tools/live/live_test",
        "tests/regression/tools/snapshots/test_kernel",
        "tests/regression/tools/notification/notification",
        "tests/regression/tools/notification/test_notification_kernel_capture",
        "tests/regression/tools/live/test_ust_tracefile_count",
        "tests/regression/tools/streaming/test_high_throughput_limits",
        "tests/stress/test_multi_sessions_per_uid_5app_streaming_kill_relayd",
        "tests/regression/tools/notification/test_notification_kernel_instrumentation",
        "tests/regression/tools/mi/test_mi",
        "tests/regression/tools/live/test_ust",
        "tests/unit/test_kprobe_event_rule_event_name",
        "tests/stress/test_multi_sessions_per_uid_10app",
        "tests/regression/tools/notification/test_notification_ust_capture",
        "tests/unit/test_session",
        "tests/utils/tap/tap.sh",
        "tests/regression/tools/rotation/rotate_utils.sh",
        "tests/regression/tools/trigger/hidden/test_hidden_trigger",
        "tests/regression/tools/rotation/test_ust_kernel",
        "tests/regression/tools/health/test_thread_ok",
        "tests/regression/tools/trigger/rate-policy/test_ust_rate_policy",
        "tests/utils/testapp/gen-ust-events-constructor/gen-ust-events-c-constructor-so",
        "tests/regression/kernel/test_select_poll_epoll",
        "tests/regression/tools/trigger/utils/notification-client",
        "tests/regression/tools/trigger/name/test_trigger_name_backwards_compat",
        "tests/unit/test_action",
        "tests/regression/tools/notification/test_notification_kernel_userspace_probe",
        "tests/regression/kernel/test_all_events",
        "tests/regression/tools/trigger/name/trigger_name",
        "tests/unit/ini_config/test_ini_config",
        "tests/regression/tools/notification/rotation",
        "tests/regression/tools/rotation/test_kernel",
        "tests/regression/tools/snapshots/test_ust_long",
        "tests/regression/ust/java-jul/test_ust_app_ctl_path_separators.sh",
        "tests/regression/kernel/test_callstack",
        "tests/regression/tools/rotation/schedule_api",
        "tests/regression/tools/wildcard/test_event_wildcard",
        "tests/unit/test_event_rule",
        "tests/unit/test_uprobe_event_rule_event_name",
        "tests/regression/kernel/test_clock_override",
        "tests/regression/tools/tracefile-limits/test_tracefile_size",
        "tests/regression/tools/trigger/test_list_triggers_cli",
        "tests/regression/tools/save-load/test_autoload",
        "tests/regression/ust/ust-app-ctl-paths/test_ust_app_ctl_paths",
    ]

    # Directories relative to project root
    directories = ["tests/"]
    if shutil.which("shellcheck") is None:
        tap.missing_platform_requirement("shellcheck not found in path")

    files = identify_shell_files(project_root, directories, exclusions)
    tap = lttngtest.TapGenerator(len(files))
    for f in files:
        # if (
        #     str(f.relative_to(project_root))
        #     != "tests/regression/tools/base-path/test_ust"
        # ):
        #     continue

        # print(f.relative_to(project_root))
        args = ["shellcheck", "--external-sources", f]
        p = subprocess.Popen(args, cwd=str(project_root))
        p.wait()
        tap.test(
            p.returncode == 0, "Shellcheck for '{}' exited without error".format(f)
        )

    sys.exit(0 if tap.is_successful else 1)
