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

from __future__ import annotations

from pathlib import Path

import pytest

from glreview.config import Config, PriorityRule, find_project_root, load_config


class TestPriorityRule:
    """Tests for PriorityRule dataclass."""

    def test_default_values(self):
        """Default values are correct."""
        rule = PriorityRule(pattern="*.py")
        assert rule.pattern == "*.py"
        assert rule.level == "medium"
        assert rule.reviewers_required == 1

    def test_custom_values(self):
        """Custom values are stored."""
        rule = PriorityRule(
            pattern="src/core/*.py",
            level="critical",
            reviewers_required=2,
        )
        assert rule.pattern == "src/core/*.py"
        assert rule.level == "critical"
        assert rule.reviewers_required == 2

    def test_matches_exact(self):
        """Matches exact filename."""
        rule = PriorityRule(pattern="main.py")
        assert rule.matches("main.py")
        assert not rule.matches("other.py")

    def test_matches_wildcard(self):
        """Matches with wildcard."""
        rule = PriorityRule(pattern="*.py")
        assert rule.matches("main.py")
        assert rule.matches("test.py")
        assert not rule.matches("main.txt")

    def test_matches_glob_pattern(self):
        """Matches with glob pattern.

        Note: fnmatch's ** requires at least one directory level,
        unlike glob's ** which matches zero or more directories.
        """
        rule = PriorityRule(pattern="src/**/*.py")
        # fnmatch ** matches one or more chars, so needs a subdir
        assert rule.matches("src/subdir/module.py")
        assert not rule.matches("tests/test.py")
        # src/module.py doesn't match because ** needs something to match
        assert not rule.matches("src/module.py")

    def test_matches_specific_directory(self):
        """Matches specific directory pattern."""
        rule = PriorityRule(pattern="src/core/*.py")
        assert rule.matches("src/core/main.py")
        assert not rule.matches("src/utils/main.py")


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

    def test_default_values(self):
        """Default values are correct."""
        config = Config()
        assert config.sources == ["src/**/*.py"]
        assert config.exclude == ["**/_version.py"]
        assert config.registry == "review_registry.json"
        assert config.issue_labels == ["review"]
        assert config.issue_template is None
        assert config.priority_rules == []
        assert config.teams == {}

    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_get_priority_no_rules(self):
        """get_priority returns default when no rules."""
        config = Config()
        level, required = config.get_priority("src/module.py")
        assert level == "medium"
        assert required == 1

    def test_get_priority_matching_rule(self):
        """get_priority returns matching rule."""
        config = Config(
            priority_rules=[
                PriorityRule(pattern="src/core/*.py", level="critical", reviewers_required=2),
                PriorityRule(pattern="src/**/*.py", level="high", reviewers_required=1),
            ]
        )
        # First rule matches
        level, required = config.get_priority("src/core/main.py")
        assert level == "critical"
        assert required == 2

        # Second rule matches
        level, required = config.get_priority("src/utils/helpers.py")
        assert level == "high"
        assert required == 1

    def test_get_priority_first_match_wins(self):
        """get_priority returns first matching rule."""
        config = Config(
            priority_rules=[
                PriorityRule(pattern="**/*.py", level="low"),
                PriorityRule(pattern="src/**/*.py", level="high"),
            ]
        )
        # First rule matches even though second also would
        level, _ = config.get_priority("src/module.py")
        assert level == "low"

    def test_get_priority_no_match(self):
        """get_priority returns default when no rules match."""
        config = Config(
            priority_rules=[
                PriorityRule(pattern="src/core/*.py", level="critical"),
            ]
        )
        level, required = config.get_priority("lib/module.py")
        assert level == "medium"
        assert required == 1

    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 == ["src/**/*.py"]
        assert config.exclude == ["**/_version.py"]
        assert config.root == temp_dir

    def test_load_from_pyproject(self, temp_dir):
        """Loads config from pyproject.toml."""
        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"]

[[tool.glreview.priority]]
pattern = "lib/core/*.py"
level = "critical"
reviewers_required = 2

[[tool.glreview.priority]]
pattern = "lib/**/*.py"
level = "high"
reviewers_required = 1
""")
        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"]}
        assert len(config.priority_rules) == 2
        assert config.priority_rules[0].pattern == "lib/core/*.py"
        assert config.priority_rules[0].level == "critical"
        assert config.priority_rules[0].reviewers_required == 2

    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 == ["**/_version.py"]
        assert config.registry == "review_registry.json"

    def test_load_empty_glreview_section(self, temp_dir):
        """Handles empty glreview section."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]
""")
        config = load_config(temp_dir)
        assert config.sources == ["src/**/*.py"]

    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 == ["src/**/*.py"]

    def test_load_priority_rule_defaults(self, temp_dir):
        """Priority rule uses defaults for missing fields."""
        pyproject = temp_dir / "pyproject.toml"
        pyproject.write_text("""
[tool.glreview]

[[tool.glreview.priority]]
pattern = "src/*.py"
""")
        config = load_config(temp_dir)

        assert len(config.priority_rules) == 1
        rule = config.priority_rules[0]
        assert rule.pattern == "src/*.py"
        assert rule.level == "medium"
        assert rule.reviewers_required == 1


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()
