import os
import unittest
from concurrent.futures import ThreadPoolExecutor

from ci.paths import PROJECT_ROOT

SRC_ROOT = PROJECT_ROOT / "src"

NUM_WORKERS = (os.cpu_count() or 1) * 4

# Files that are allowed to not have #pragma once
EXCLUDED_FILES = [
    # Add any exceptions here
]

EXCLUDED_DIRS = [
    "third_party",
    "platforms",
]


class TestMissingPragmaOnce(unittest.TestCase):

    def check_file(self, file_path: str) -> list[str]:
        """Check if a header file has #pragma once directive or if a cpp file incorrectly has it."""
        failings: list[str] = []

        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read()

            if file_path.endswith(".h"):
                # For header files, check if #pragma once is missing
                if "#pragma once" not in content:
                    failings.append(f"Missing #pragma once in {file_path}")
            elif file_path.endswith(".cpp"):
                # For cpp files, check if #pragma once is incorrectly present
                if "#pragma once" in content:
                    failings.append(f"Incorrect #pragma once in cpp file: {file_path}")

        return failings

    def test_pragma_once_usage(self) -> None:
        """
        Searches through files to:
        1. Check for missing #pragma once in header files
        2. Check for incorrect #pragma once in cpp files
        """
        files_to_check = []
        current_dir = None

        # Collect files to check
        for root, dirs, files in os.walk(SRC_ROOT):
            # Log when we enter a new directory
            rel_path = os.path.relpath(root, SRC_ROOT)
            if current_dir != rel_path:
                current_dir = rel_path
                print(f"Traversing directory: {rel_path}")
                if rel_path in EXCLUDED_DIRS:
                    print(f"  Skipping excluded directory: {rel_path}")
                    dirs[:] = []  # Skip this directory and its subdirectories
                    continue

            # Check if this directory should be excluded
            # if any(os.path.normpath(root).startswith(os.path.normpath(excluded_dir))
            #        for excluded_dir in EXCLUDED_DIRS):
            #     print(f"  Skipping excluded directory: {rel_path}")
            #     continue
            for excluded_dir in EXCLUDED_DIRS:
                npath = os.path.normpath(root)
                npath_excluded = os.path.normpath(excluded_dir)
                print(f"Checking {npath} against excluded {npath_excluded}")
                if npath.startswith(npath_excluded):
                    print(f"  Skipping excluded directory: {rel_path}")
                    break

            for file in files:
                if file.endswith((".h", ".cpp")):  # Check both header and cpp files
                    file_path = os.path.join(root, file)

                    # Check if file is excluded
                    # if any(file_path.endswith(excluded) for excluded in EXCLUDED_FILES):
                    #     print(f"  Skipping excluded file: {file}")
                    #     continue
                    for excluded in EXCLUDED_FILES:
                        # print(f"Checking {file_path} against excluded {excluded}")
                        if file_path.endswith(excluded):
                            print(f"  Skipping excluded file: {file}")
                            break

                    files_to_check.append(file_path)

        print(f"Found {len(files_to_check)} files to check")

        # Process files in parallel
        all_failings = []
        with ThreadPoolExecutor(max_workers=NUM_WORKERS) as executor:
            futures = [
                executor.submit(self.check_file, file_path)
                for file_path in files_to_check
            ]
            for future in futures:
                all_failings.extend(future.result())

        # Report results
        if all_failings:
            msg = f"Found {len(all_failings)} pragma once issues: \n" + "\n".join(
                all_failings
            )
            for failing in all_failings:
                print(failing)
            self.fail(msg)
        else:
            print("All files have proper pragma once usage.")

        print(f"Pragma once check completed. Processed {len(files_to_check)} files.")


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