"""Tests for glreview.config module."""

from __future__ import annotations

from pathlib import Path

import pytest

from glreview.config import Config, find_project_root, load_config
from glreview.errors import ConfigurationError


class TestConfig:
    """Tests for Config dataclass."""

    def test_default_values(self):
        """Default values are correct."""
        config = Config()
        assert config.sources == []  # No default - must be configured
        assert config.exclude == []  # No default
        assert config.registry == ".glreview/registry.json"
        assert config.issue_labels == ["review::pending"]
        assert config.issue_template is None
        assert config.teams == {}

    def test_default_claude_instructions(self):
        """Claude instruction fields default to None."""
        config = Config()
        assert config.fix_instructions is None
        assert config.review_instructions is None

    def test_custom_values(self):
        """Custom values are stored."""
        config = Config(
            sources=["lib/**/*.py"],
            exclude=["**/test_*.py"],
            registry="custom_registry.json",
            issue_labels=["review", "code-quality"],
            issue_template="templates/issue.md",
            teams={"core": ["@alice", "@bob"]},
        )
        assert config.sources == ["lib/**/*.py"]
        assert config.exclude == ["**/test_*.py"]
        assert config.registry == "custom_registry.json"
        assert config.issue_labels == ["review", "code-quality"]
        assert config.issue_template == "templates/issue.md"
        assert config.teams == {"core": ["@alice", "@bob"]}

    def test_registry_path(self, temp_dir):
        """registry_path returns absolute path."""
        config = Config(root=temp_dir, registry="my_registry.json")
        assert config.registry_path == temp_dir / "my_registry.json"


class TestLoadConfig:
    """Tests for load_config function."""

    def test_load_defaults_no_pyproject(self, temp_dir):
        """Returns defaults when no pyproject.toml."""
        config = load_config(temp_dir)
        assert config.sources == []  # No default - must be configured
        assert config.exclude == []
        assert config.root == temp_dir

    def test_load_from_pyproject(self, temp_dir):
        """Loads config from pyproject.toml."""
        # Create the template file so validation passes
        templates = temp_dir / "templates"
        templates.mkdir()
        (templates / "review_issue.md").write_text("## Review\n{{ path }}\n")

        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
sources = ["lib/**/*.py", "src/**/*.py"]
exclude = ["**/test_*.py", "**/_version.py"]
registry = "custom_registry.json"
issue_labels = ["review", "needs-attention"]
issue_template = "templates/review_issue.md"

[tool.glreview.teams]
core = ["@alice", "@bob"]
docs = ["@charlie"]
""")
        config = load_config(temp_dir)

        assert config.sources == ["lib/**/*.py", "src/**/*.py"]
        assert config.exclude == ["**/test_*.py", "**/_version.py"]
        assert config.registry == "custom_registry.json"
        assert config.issue_labels == ["review", "needs-attention"]
        assert config.issue_template == "templates/review_issue.md"
        assert config.teams == {"core": ["@alice", "@bob"], "docs": ["@charlie"]}

    def test_load_partial_config(self, temp_dir):
        """Loads partial config, using defaults for missing."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
sources = ["custom/**/*.py"]
""")
        config = load_config(temp_dir)

        assert config.sources == ["custom/**/*.py"]
        # Defaults for missing
        assert config.exclude == []
        assert config.registry == ".glreview/registry.json"

    def test_load_empty_glreview_section(self, temp_dir):
        """Handles empty glreview section - sources required but empty."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
""")
        config = load_config(temp_dir)
        assert config.sources == []  # No default

    def test_load_claude_instructions(self, temp_dir):
        """Loads claude fix_instructions and review_instructions from pyproject.toml."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
sources = ["src/**/*.py"]

[tool.glreview.claude]
fix_instructions = "Run pytest after fixing."
review_instructions = "Use numpy-style docstrings."
""")
        config = load_config(temp_dir)
        assert config.fix_instructions == "Run pytest after fixing."
        assert config.review_instructions == "Use numpy-style docstrings."

    def test_load_claude_instructions_defaults_to_none(self, temp_dir):
        """Claude instructions default to None when not specified."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
sources = ["src/**/*.py"]
""")
        config = load_config(temp_dir)
        assert config.fix_instructions is None
        assert config.review_instructions is None

    def test_load_no_glreview_section(self, temp_dir):
        """Handles pyproject.toml without glreview section."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.black]
line-length = 88
""")
        config = load_config(temp_dir)
        assert config.sources == []  # No default


class TestConfigValidation:
    """Tests for Config.validate method."""

    def test_empty_sources_raises(self, temp_dir):
        """Raises ConfigurationError when sources is empty."""
        config = Config(root=temp_dir, sources=[])
        with pytest.raises(ConfigurationError, match="No source patterns"):
            config.validate()

    def test_valid_sources_passes(self, temp_dir):
        """Passes validation with valid sources."""
        config = Config(root=temp_dir, sources=["src/**/*.py"])
        config.validate()  # Should not raise

    def test_missing_template_raises(self, temp_dir):
        """Raises ConfigurationError when issue_template file doesn't exist."""
        config = Config(
            root=temp_dir,
            sources=["src/**/*.py"],
            issue_template="templates/nonexistent.md",
        )
        with pytest.raises(ConfigurationError, match="Issue template not found"):
            config.validate()

    def test_valid_template_passes(self, temp_dir):
        """Passes validation when issue_template file exists."""
        template_dir = temp_dir / "templates"
        template_dir.mkdir()
        (template_dir / "issue.md").write_text("# Issue\n")

        config = Config(
            root=temp_dir,
            sources=["src/**/*.py"],
            issue_template="templates/issue.md",
        )
        config.validate()  # Should not raise

    def test_load_config_validates_when_section_exists(self, temp_dir):
        """load_config calls validate when [tool.glreview] exists with empty sources."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
sources = []
""")
        with pytest.raises(ConfigurationError, match="No source patterns"):
            load_config(temp_dir)

    def test_load_config_skips_validation_without_section(self, temp_dir):
        """load_config does not validate when no [tool.glreview] section."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.black]
line-length = 88
""")
        # Should not raise even though sources is empty
        config = load_config(temp_dir)
        assert config.sources == []


class TestFindProjectRoot:
    """Tests for find_project_root function."""

    def test_finds_pyproject(self, temp_dir):
        """Finds directory with pyproject.toml."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("[project]\nname = 'test'\n")

        subdir = temp_dir / "src" / "subdir"
        subdir.mkdir(parents=True)

        root = find_project_root(subdir)
        # Resolve both to handle symlinks (e.g., macOS /var -> /private/var)
        assert root == temp_dir.resolve()

    def test_finds_git_dir(self, temp_dir):
        """Finds directory with .git."""
        git_dir = temp_dir / ".git"
        git_dir.mkdir()

        subdir = temp_dir / "src" / "subdir"
        subdir.mkdir(parents=True)

        root = find_project_root(subdir)
        # Resolve both to handle symlinks (e.g., macOS /var -> /private/var)
        assert root == temp_dir.resolve()

    def test_prefers_pyproject_over_git(self, temp_dir):
        """Prefers pyproject.toml over .git when both exist."""
        # Create .git in parent
        git_dir = temp_dir / ".git"
        git_dir.mkdir()

        # Create pyproject.toml in subdir
        subdir = temp_dir / "subproject"
        subdir.mkdir()
        pyproject = subdir / "pyproject.toml"
        pyproject.write_text("[project]\nname = 'test'\n")

        inner = subdir / "src"
        inner.mkdir()

        root = find_project_root(inner)
        # Resolve both to handle symlinks (e.g., macOS /var -> /private/var)
        assert root == subdir.resolve()

    def test_falls_back_to_start(self, temp_dir):
        """Falls back to start directory when nothing found."""
        subdir = temp_dir / "orphan"
        subdir.mkdir()

        root = find_project_root(subdir)
        assert root == subdir.resolve()

    def test_defaults_to_cwd(self):
        """Defaults to current working directory."""
        # This should not raise
        root = find_project_root()
        assert root.exists()
