"""Tests for file discovery module.

Tests discover_files, _run_git_ls_files, _classify_language from analysis/discovery.py.
Uses tmp_path for file-based tests, mocks subprocess for git commands.
"""

from __future__ import annotations

import subprocess
from typing import TYPE_CHECKING
from unittest.mock import patch

import pytest

from unfamiliar.analysis.adapters import create_default_registry
from unfamiliar.analysis.discovery import (
    AnalysisError,
    _classify_language,
    _run_git_ls_files,
    discover_files,
)

if TYPE_CHECKING:
    from pathlib import Path


class TestClassifyLanguage:
    """Tests for _classify_language helper."""

    def test_py_file_returns_python(self):
        """.py extension returns 'python'."""
        registry = create_default_registry()
        result = _classify_language("src/main.py", registry)
        assert result == "python"

    def test_ts_file_returns_typescript(self):
        """.ts extension returns 'typescript'."""
        registry = create_default_registry()
        result = _classify_language("src/app.ts", registry)
        assert result == "typescript"

    def test_tsx_file_returns_typescript(self):
        """.tsx extension returns 'typescript'."""
        registry = create_default_registry()
        result = _classify_language("src/component.tsx", registry)
        assert result == "typescript"

    def test_js_file_returns_javascript(self):
        """.js extension returns 'javascript'."""
        registry = create_default_registry()
        result = _classify_language("src/index.js", registry)
        assert result == "javascript"

    def test_jsx_file_returns_javascript(self):
        """.jsx extension returns 'javascript'."""
        registry = create_default_registry()
        result = _classify_language("src/component.jsx", registry)
        assert result == "javascript"

    def test_unknown_extension_returns_unknown(self):
        """Unrecognized extension returns 'unknown'."""
        registry = create_default_registry()
        result = _classify_language("data.csv", registry)
        assert result == "unknown"

    def test_no_extension_returns_unknown(self):
        """File with no extension returns 'unknown'."""
        registry = create_default_registry()
        result = _classify_language("README", registry)
        assert result == "unknown"

    def test_markdown_returns_unknown(self):
        """Non-code file returns 'unknown'."""
        registry = create_default_registry()
        result = _classify_language("docs/guide.md", registry)
        assert result == "unknown"


class TestRunGitLsFiles:
    """Tests for _run_git_ls_files helper."""

    def test_returns_file_list_on_success(self, tmp_path: Path):
        """Successful git ls-files returns list of paths."""
        mock_output = "src/main.py\nsrc/utils.py\nREADME.md"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = subprocess.CompletedProcess(
                args=["git", "ls-files"],
                returncode=0,
                stdout=mock_output,
                stderr="",
            )
            result = _run_git_ls_files(tmp_path)

        assert result == ["src/main.py", "src/utils.py", "README.md"]

    def test_filters_empty_lines(self, tmp_path: Path):
        """Empty lines are filtered from output."""
        mock_output = "src/main.py\n\n\nsrc/utils.py\n"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = subprocess.CompletedProcess(
                args=["git", "ls-files"],
                returncode=0,
                stdout=mock_output,
                stderr="",
            )
            result = _run_git_ls_files(tmp_path)

        assert result == ["src/main.py", "src/utils.py"]

    def test_raises_analysis_error_on_git_failure(self, tmp_path: Path):
        """Git command failure raises AnalysisError."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = subprocess.CalledProcessError(
                returncode=128,
                cmd=["git", "ls-files"],
                stderr="fatal: not a git repository",
            )
            with pytest.raises(AnalysisError) as exc_info:
                _run_git_ls_files(tmp_path)

        assert "git ls-files failed" in str(exc_info.value)

    def test_raises_analysis_error_when_git_not_found(self, tmp_path: Path):
        """Missing git executable raises AnalysisError."""
        with patch("subprocess.run") as mock_run:
            mock_run.side_effect = FileNotFoundError("git not found")
            with pytest.raises(AnalysisError) as exc_info:
                _run_git_ls_files(tmp_path)

        assert "git executable not found" in str(exc_info.value)


class TestDiscoverFiles:
    """Tests for discover_files function."""

    def test_returns_sorted_file_infos(self, tmp_path: Path):
        """discover_files returns FileInfo list sorted by path."""
        # Set up fake git repo with files
        (tmp_path / ".git").mkdir()
        (tmp_path / "src").mkdir()
        (tmp_path / "src" / "main.py").write_text("print('hello')")
        (tmp_path / "src" / "utils.py").write_text("def helper(): pass")

        mock_output = "src/utils.py\nsrc/main.py"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = subprocess.CompletedProcess(
                args=["git", "ls-files"],
                returncode=0,
                stdout=mock_output,
                stderr="",
            )
            registry = create_default_registry()
            files, excluded = discover_files(tmp_path, registry)

        # Should be sorted alphabetically
        assert len(files) == 2
        assert files[0].path == "src/main.py"
        assert files[1].path == "src/utils.py"
        assert files[0].language == "python"
        assert files[1].language == "python"
        # No filter, so excluded should be 0
        assert excluded == 0

    def test_skips_binary_files(self, tmp_path: Path):
        """Binary files are skipped."""
        (tmp_path / ".git").mkdir()
        (tmp_path / "binary.bin").write_bytes(b"\x00\x01\x02\xff")
        (tmp_path / "text.py").write_text("print('hello')")

        mock_output = "binary.bin\ntext.py"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = subprocess.CompletedProcess(
                args=["git", "ls-files"],
                returncode=0,
                stdout=mock_output,
                stderr="",
            )
            registry = create_default_registry()
            files, excluded = discover_files(tmp_path, registry)

        assert len(files) == 1
        assert files[0].path == "text.py"

    def test_skips_directories(self, tmp_path: Path):
        """Directories returned by git are skipped."""
        (tmp_path / ".git").mkdir()
        (tmp_path / "src").mkdir()
        (tmp_path / "src" / "main.py").write_text("print('hello')")

        # git ls-files sometimes returns submodule paths as directories
        mock_output = "src\nsrc/main.py"
        with patch("subprocess.run") as mock_run:
            mock_run.return_value = subprocess.CompletedProcess(
                args=["git", "ls-files"],
                returncode=0,
                stdout=mock_output,
                stderr="",
            )
            registry = create_default_registry()
            files, excluded = discover_files(tmp_path, registry)

        assert len(files) == 1
        assert files[0].path == "src/main.py"
