"""Tests that repository integrity guardrails remain intact.

These tests verify structural invariants of the file whitelist and
CODEOWNERS that protect against accidental re-introduction of broad
file-type exceptions (especially binary wildcards).
"""

import re
from pathlib import Path

import pytest

REPO_ROOT = Path(__file__).resolve().parents[2]

# Binary extensions that MUST be path-anchored (start with ^) in the whitelist.
# Broad binary wildcards like `\.png$` permanently bloat git history.
BINARY_EXTENSIONS = {
    "png",
    "jpg",
    "jpeg",
    "gif",
    "bmp",
    "tiff",
    "webp",
    "svg",
    "ico",
    "mp3",
    "mp4",
    "wav",
    "ogg",
    "m4a",
    "webm",
    "exe",
    "dll",
    "so",
    "dylib",
    "bin",
    "zip",
    "tar",
    "gz",
    "rar",
    "7z",
    "pdf",
    "doc",
    "docx",
    "xls",
    "xlsx",
    "db",
    "sqlite",
    "sqlite3",
    "woff",
    "woff2",
    "ttf",
    "eot",
}

# These guardrail paths must be the LAST CODEOWNERS entries (last-match-wins).
CODEOWNERS_GUARDRAIL_PATHS = {
    "/.gitignore",
    "/.file-whitelist.txt",
    "/.pre-commit-hooks/file-whitelist-check.sh",
    "/.github/scripts/file-whitelist-check.sh",
    "/.github/CODEOWNERS",
}


class TestFileWhitelistGuardrails:
    """Verify .file-whitelist.txt doesn't contain broad binary wildcards."""

    @pytest.fixture()
    def whitelist_patterns(self):
        """Load non-comment, non-empty lines from .file-whitelist.txt."""
        whitelist = REPO_ROOT / ".file-whitelist.txt"
        assert whitelist.exists(), ".file-whitelist.txt not found at repo root"
        lines = []
        for line in whitelist.read_text().splitlines():
            stripped = line.strip()
            if stripped and not stripped.startswith("#"):
                lines.append(stripped)
        return lines

    def test_binary_patterns_are_path_anchored(self, whitelist_patterns):
        """Binary extension patterns must start with ^ (path-scoped).

        A broad pattern like `\\.png$` would allow PNGs anywhere in the repo,
        defeating the purpose of the whitelist. Binary patterns must be
        anchored to specific paths, e.g. `^docs/images/specific\\.png$`.
        """
        violations = []
        for pattern in whitelist_patterns:
            # Check if this pattern matches a binary extension
            for ext in BINARY_EXTENSIONS:
                # Pattern ends with the extension (escaped dot + ext + $)
                if re.search(rf"\\\.{re.escape(ext)}\)?(\|.*)?$", pattern):
                    if not pattern.startswith("^"):
                        violations.append(
                            f"Unanchored binary wildcard: '{pattern}' — "
                            f"must start with ^ to scope to a specific path"
                        )
                    break
        assert not violations, (
            "Binary file patterns in .file-whitelist.txt must be path-anchored "
            "(start with ^) to prevent repo bloat.\n"
            + "\n".join(f"  - {v}" for v in violations)
        )

    def test_no_broad_binary_wildcards(self, whitelist_patterns):
        """Binary extension patterns must not use .* wildcards.

        A pattern like `^docs/images/.*\\.png$` allows unlimited binary files
        in a directory. Binary files should be listed by explicit path to
        prevent repo bloat. Each new binary file should be a deliberate,
        reviewed addition.
        """
        violations = []
        for pattern in whitelist_patterns:
            for ext in BINARY_EXTENSIONS:
                if re.search(rf"\\\.{re.escape(ext)}\)?(\|.*)?$", pattern):
                    if ".*" in pattern:
                        violations.append(
                            f"Broad binary wildcard: '{pattern}' — "
                            f"uses .* which allows unlimited binary files; "
                            f"list each binary file explicitly instead"
                        )
                    break
        assert not violations, (
            "Binary file patterns must not use .* wildcards. "
            "List each binary file by explicit path to prevent repo bloat.\n"
            + "\n".join(f"  - {v}" for v in violations)
        )

    def test_whitelist_file_exists(self):
        """The shared whitelist file must exist."""
        assert (REPO_ROOT / ".file-whitelist.txt").exists()

    def test_whitelist_has_patterns(self, whitelist_patterns):
        """Whitelist must contain at least some patterns."""
        assert len(whitelist_patterns) > 10, (
            f"Whitelist only has {len(whitelist_patterns)} patterns — "
            "seems too few, file may be corrupted"
        )


class TestCodeownersGuardrails:
    """Verify CODEOWNERS guardrail rules remain at the bottom."""

    @pytest.fixture()
    def codeowners_rules(self):
        """Load non-comment, non-empty lines from CODEOWNERS."""
        codeowners = REPO_ROOT / ".github" / "CODEOWNERS"
        assert codeowners.exists(), ".github/CODEOWNERS not found"
        rules = []
        for line in codeowners.read_text().splitlines():
            stripped = line.strip()
            if stripped and not stripped.startswith("#"):
                rules.append(stripped)
        return rules

    def test_guardrail_rules_are_last(self, codeowners_rules):
        """Guardrail CODEOWNERS entries must be the last rules in the file.

        GitHub CODEOWNERS uses last-match-wins. If someone adds a rule
        after the guardrails (e.g. `* @someone`), it would override the
        maintainer-only restriction on .gitignore and whitelist files.
        """
        last_n = codeowners_rules[-len(CODEOWNERS_GUARDRAIL_PATHS) :]
        actual_paths = {line.split()[0] for line in last_n}
        assert actual_paths == CODEOWNERS_GUARDRAIL_PATHS, (
            f"CODEOWNERS guardrail rules are not the last entries!\n"
            f"Expected last {len(CODEOWNERS_GUARDRAIL_PATHS)} rules to be: "
            f"{CODEOWNERS_GUARDRAIL_PATHS}\n"
            f"Got: {actual_paths}\n"
            "Someone may have added rules after the guardrails, which would "
            "override the maintainer-only restriction (last-match-wins)."
        )

    def test_guardrail_rules_are_maintainer_only(self, codeowners_rules):
        """Guardrail rules must be restricted to @LearningCircuit only."""
        last_n = codeowners_rules[-len(CODEOWNERS_GUARDRAIL_PATHS) :]
        for rule in last_n:
            parts = rule.split()
            path = parts[0]
            owners = parts[1:]
            if path in CODEOWNERS_GUARDRAIL_PATHS:
                assert owners == ["@LearningCircuit"], (
                    f"Guardrail rule '{path}' has owners {owners} — "
                    "must be restricted to @LearningCircuit only"
                )
