"""Tests for analysis/filters/ module.

Covers:
- gitignore.py: gitignore spec loading
- patterns.py: FileFilter class and pattern matching
"""

from pathlib import Path

from unfamiliar.analysis.filters.gitignore import load_gitignore_spec
from unfamiliar.analysis.filters.patterns import FileFilter


class TestLoadGitignoreSpec:
    """Tests for load_gitignore_spec function."""

    def test_valid_gitignore_returns_matching_spec(self, tmp_path: Path) -> None:
        """load_gitignore_spec returns spec that matches patterns from .gitignore."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("*.pyc\n__pycache__/\n")

        result = load_gitignore_spec(tmp_path)

        assert result.match_file("test.pyc") is True
        assert result.match_file("main.py") is False

    def test_missing_gitignore_returns_none(self, tmp_path: Path) -> None:
        """load_gitignore_spec returns None when .gitignore doesn't exist."""
        result = load_gitignore_spec(tmp_path)
        assert result is None

    def test_empty_gitignore_returns_none(self, tmp_path: Path) -> None:
        """load_gitignore_spec returns None for empty .gitignore."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("")

        result = load_gitignore_spec(tmp_path)
        assert result is None

    def test_whitespace_only_gitignore_returns_none(self, tmp_path: Path) -> None:
        """load_gitignore_spec returns None for whitespace-only .gitignore."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("   \n\t\n  ")

        result = load_gitignore_spec(tmp_path)
        assert result is None

    def test_unicode_content_handled_correctly(self, tmp_path: Path) -> None:
        """load_gitignore_spec handles UTF-8 content with emoji comments."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("# Comment with emoji 🎉\n*.pyc\n")

        result = load_gitignore_spec(tmp_path)

        assert result.match_file("test.pyc") is True

    def test_complex_patterns_parsed_correctly(self, tmp_path: Path) -> None:
        """load_gitignore_spec handles character classes, directories, and negations."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("*.py[cod]\n__pycache__/\ndist/\nbuild/\n!important.txt\n")

        result = load_gitignore_spec(tmp_path)

        assert result.match_file("test.pyc") is True
        assert result.match_file("test.pyo") is True
        assert result.match_file("__pycache__/module.pyc") is True
        assert result.match_file("dist/package.whl") is True
        assert result.match_file("build/output.o") is True
        assert result.match_file("important.txt") is False  # negated


class TestFileFilterAlwaysExclude:
    """Tests for ALWAYS_EXCLUDE layer (hard exclusions that cannot be overridden)."""

    def test_git_directory_excluded(self) -> None:
        """Files inside .git/ directory are always excluded."""
        filt = FileFilter()

        assert filt.should_exclude(".git/config") is True
        assert filt.should_exclude(".git/HEAD") is True

    def test_pycache_directory_excluded(self) -> None:
        """Files inside __pycache__/ directories are always excluded."""
        filt = FileFilter()

        assert filt.should_exclude("__pycache__/module.pyc") is True
        assert filt.should_exclude("src/__pycache__/main.pyc") is True

    def test_compiled_python_files_excluded(self) -> None:
        """Compiled .pyc files are always excluded regardless of location."""
        filt = FileFilter()

        assert filt.should_exclude("module.pyc") is True
        assert filt.should_exclude("src/utils.pyc") is True

    def test_node_modules_excluded(self) -> None:
        """Files inside node_modules/ are always excluded."""
        filt = FileFilter()

        assert filt.should_exclude("node_modules/lodash/index.js") is True
        assert filt.should_exclude("node_modules/react/package.json") is True

    def test_ide_directories_excluded(self) -> None:
        """IDE config directories like .idea/ and .vscode/ are excluded."""
        filt = FileFilter()

        assert filt.should_exclude(".idea/workspace.xml") is True
        assert filt.should_exclude(".vscode/settings.json") is True

    def test_minified_assets_excluded(self) -> None:
        """Minified JS and CSS files are always excluded."""
        filt = FileFilter()

        assert filt.should_exclude("bundle.min.js") is True
        assert filt.should_exclude("styles.min.css") is True

    def test_egg_info_excluded(self) -> None:
        """Python egg-info directories are always excluded."""
        filt = FileFilter()

        assert filt.should_exclude("my_package.egg-info/PKG-INFO") is True


class TestFileFilterDefaultExclude:
    """Tests for DEFAULT_EXCLUDE layer (soft exclusions that can be overridden)."""

    def test_markdown_files_excluded_by_default(self) -> None:
        """Markdown files are excluded unless overridden by include patterns."""
        filt = FileFilter()

        assert filt.should_exclude("README.md") is True
        assert filt.should_exclude("docs/guide.md") is True

    def test_json_files_excluded_by_default(self) -> None:
        """JSON files are excluded unless overridden."""
        filt = FileFilter()

        assert filt.should_exclude("config.json") is True
        assert filt.should_exclude("data/output.json") is True

    def test_yaml_files_excluded_by_default(self) -> None:
        """YAML files are excluded unless overridden."""
        filt = FileFilter()

        assert filt.should_exclude("config.yaml") is True
        assert filt.should_exclude("docker-compose.yml") is True

    def test_env_files_excluded(self) -> None:
        """Environment files with various naming patterns are excluded."""
        filt = FileFilter()

        assert filt.should_exclude(".env") is True
        assert filt.should_exclude(".env.local") is True
        assert filt.should_exclude("prod.env") is True

    def test_media_files_excluded(self) -> None:
        """Image and video files are excluded by default."""
        filt = FileFilter()

        assert filt.should_exclude("logo.png") is True
        assert filt.should_exclude("photo.jpg") is True
        assert filt.should_exclude("icon.svg") is True
        assert filt.should_exclude("video.mp4") is True

    def test_markup_and_styles_excluded(self) -> None:
        """HTML and CSS files are excluded by default."""
        filt = FileFilter()

        assert filt.should_exclude("index.html") is True
        assert filt.should_exclude("styles.css") is True
        assert filt.should_exclude("theme.scss") is True


class TestFileFilterAlwaysInclude:
    """Tests for ALWAYS_INCLUDE layer (overrides DEFAULT_EXCLUDE for specific files)."""

    def test_package_json_included_despite_json_exclusion(self) -> None:
        """package.json is included even though .json files are normally excluded."""
        filt = FileFilter()

        assert filt.should_exclude("package.json") is False

    def test_pyproject_toml_included(self) -> None:
        """pyproject.toml is included for Python dependency analysis."""
        filt = FileFilter()

        assert filt.should_exclude("pyproject.toml") is False

    def test_requirements_files_included(self) -> None:
        """requirements.txt files are included for dependency analysis."""
        filt = FileFilter()

        assert filt.should_exclude("requirements.txt") is False
        assert filt.should_exclude("requirements-dev.txt") is False

    def test_lock_files_included(self) -> None:
        """Package lock files are included despite being JSON/YAML."""
        filt = FileFilter()

        assert filt.should_exclude("package-lock.json") is False
        assert filt.should_exclude("yarn.lock") is False
        assert filt.should_exclude("pnpm-lock.yaml") is False


class TestFileFilterUserPatterns:
    """Tests for user-provided include and exclude patterns."""

    def test_include_pattern_overrides_default_exclude(self) -> None:
        """User include patterns can override DEFAULT_EXCLUDE for specific files."""
        filt = FileFilter(include_patterns=["*.md"])

        assert filt.should_exclude("README.md") is False
        assert filt.should_exclude("docs/guide.md") is False

    def test_exclude_pattern_adds_new_exclusions(self) -> None:
        """User exclude patterns add exclusions beyond the defaults."""
        filt = FileFilter(exclude_patterns=["*.custom"])

        assert filt.should_exclude("file.custom") is True

    def test_include_and_exclude_patterns_combine(self) -> None:
        """User include and exclude patterns work together correctly."""
        filt = FileFilter(
            include_patterns=["docs/*.md"],
            exclude_patterns=["docs/internal/"],
        )

        assert filt.should_exclude("docs/guide.md") is False
        assert filt.should_exclude("docs/internal/secret.md") is True


class TestFileFilterGitignoreIntegration:
    """Tests for .gitignore integration via load_gitignore()."""

    def test_gitignore_patterns_applied_to_filter(self, tmp_path: Path) -> None:
        """Patterns from .gitignore are applied during should_exclude checks."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("secrets/\n*.secret")

        filt = FileFilter()
        filt.load_gitignore(tmp_path)

        assert filt.should_exclude("secrets/api_keys.txt") is True
        assert filt.should_exclude("config.secret") is True
        assert filt.should_exclude("main.py") is False

    def test_missing_gitignore_handled_gracefully(self, tmp_path: Path) -> None:
        """load_gitignore does not raise when .gitignore is missing."""
        filt = FileFilter()

        filt.load_gitignore(tmp_path)
        # No exception raised

    def test_gitignore_and_default_exclude_both_apply(self, tmp_path: Path) -> None:
        """Gitignore patterns work alongside DEFAULT_EXCLUDE, not replacing them."""
        gitignore = tmp_path / ".gitignore"
        gitignore.write_text("local_config.json")

        filt = FileFilter()
        filt.load_gitignore(tmp_path)

        assert filt.should_exclude("local_config.json") is True
        assert filt.should_exclude("other_config.json") is True
        assert filt.should_exclude("main.py") is False


class TestFileFilterEdgeCases:
    """Tests for edge cases and boundary conditions in should_exclude()."""

    def test_empty_path_excluded(self) -> None:
        """Empty string path is excluded."""
        filt = FileFilter()

        assert filt.should_exclude("") is True

    def test_absolute_path_excluded(self) -> None:
        """Paths starting with / are treated as invalid and excluded."""
        filt = FileFilter()

        assert filt.should_exclude("/etc/passwd") is True
        assert filt.should_exclude("/home/user/file.py") is True

    def test_nested_paths_matched_against_patterns(self) -> None:
        """Patterns correctly match files in nested directories."""
        filt = FileFilter()

        assert filt.should_exclude("src/utils/__pycache__/helpers.pyc") is True
        assert filt.should_exclude("frontend/node_modules/react/index.js") is True

    def test_source_code_files_included_by_default(self) -> None:
        """Python, TypeScript, and JavaScript source files pass through filter."""
        filt = FileFilter()

        assert filt.should_exclude("main.py") is False
        assert filt.should_exclude("src/app.ts") is False
        assert filt.should_exclude("lib/utils.js") is False
