"""Tests for health_checks module."""

import json
from pathlib import Path
from unittest.mock import patch

import pytest

from erk.core.health_checks import (
    CheckResult,
    check_claude_settings,
    check_docs_agent,
    check_erk_version,
    check_gitignore_entries,
    check_hooks_disabled,
    check_repository,
    check_uv_version,
)
from erk.core.health_checks_dogfooder.legacy_config_locations import (
    check_legacy_config_locations,
)
from erk_shared.git.fake import FakeGit
from tests.fakes.context import create_test_context


def test_check_result_dataclass() -> None:
    """Test CheckResult dataclass creation."""
    result = CheckResult(
        name="test",
        passed=True,
        message="Test passed",
        details="Some details",
    )

    assert result.name == "test"
    assert result.passed is True
    assert result.message == "Test passed"
    assert result.details == "Some details"


def test_check_result_without_details() -> None:
    """Test CheckResult without optional details."""
    result = CheckResult(
        name="test",
        passed=False,
        message="Test failed",
    )

    assert result.name == "test"
    assert result.passed is False
    assert result.message == "Test failed"
    assert result.details is None


def test_check_erk_version() -> None:
    """Test that check_erk_version returns a valid result."""
    result = check_erk_version()

    # Should always pass if erk is installed (which it is since we're running tests)
    assert result.name == "erk"
    assert result.passed is True
    assert "erk" in result.message.lower()


def test_check_claude_settings_no_file(tmp_path: Path) -> None:
    """Test claude settings check when no settings file exists."""
    result = check_claude_settings(tmp_path)

    assert result.name == "claude settings"
    assert result.passed is True
    assert "No .claude/settings.json" in result.message


def test_check_claude_settings_valid_json(tmp_path: Path) -> None:
    """Test claude settings check with valid settings file."""
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings_file = claude_dir / "settings.json"
    settings_file.write_text(json.dumps({"hooks": {}}), encoding="utf-8")

    result = check_claude_settings(tmp_path)

    assert result.name == "claude settings"
    assert result.passed is True
    assert "looks valid" in result.message.lower() or "using defaults" in result.message.lower()


def test_check_claude_settings_invalid_json(tmp_path: Path) -> None:
    """Test claude settings check with invalid JSON."""
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings_file = claude_dir / "settings.json"
    settings_file.write_text("{invalid json", encoding="utf-8")

    result = check_claude_settings(tmp_path)

    assert result.name == "claude settings"
    assert result.passed is False
    assert "Invalid JSON" in result.message


def test_check_claude_settings_with_hooks(tmp_path: Path) -> None:
    """Test claude settings check with hook configuration."""
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings = {
        "hooks": {
            "userPromptSubmit": [
                {
                    "command": "echo hello",
                }
            ]
        }
    }
    settings_file = claude_dir / "settings.json"
    settings_file.write_text(json.dumps(settings), encoding="utf-8")

    result = check_claude_settings(tmp_path)

    assert result.name == "claude settings"
    assert result.passed is True


def test_check_repository_not_in_git_repo(tmp_path: Path) -> None:
    """Test repository check when not in a git repository."""
    # FakeGit with no git_common_dirs configured returns None for get_git_common_dir
    git = FakeGit()
    ctx = create_test_context(git=git, cwd=tmp_path)

    result = check_repository(ctx)

    assert result.name == "repository"
    assert result.passed is False
    assert "Not in a git repository" in result.message


def test_check_repository_in_repo_without_erk(tmp_path: Path) -> None:
    """Test repository check in a git repo without .erk directory."""
    # Configure FakeGit to recognize tmp_path as a git repo
    git = FakeGit(
        git_common_dirs={tmp_path: tmp_path / ".git"},
        repository_roots={tmp_path: tmp_path},
    )
    ctx = create_test_context(git=git, cwd=tmp_path)

    result = check_repository(ctx)

    assert result.name == "repository"
    assert result.passed is True
    assert "no .erk/ directory" in result.message.lower()
    assert result.details is not None
    assert "erk init" in result.details


def test_check_repository_in_repo_with_erk(tmp_path: Path) -> None:
    """Test repository check in a git repo with .erk directory."""
    # Configure FakeGit to recognize tmp_path as a git repo
    git = FakeGit(
        git_common_dirs={tmp_path: tmp_path / ".git"},
        repository_roots={tmp_path: tmp_path},
    )
    ctx = create_test_context(git=git, cwd=tmp_path)

    # Create .erk directory
    erk_dir = tmp_path / ".erk"
    erk_dir.mkdir()

    result = check_repository(ctx)

    assert result.name == "repository"
    assert result.passed is True
    assert "erk setup detected" in result.message.lower()


def test_check_repository_uses_repo_root_not_cwd(tmp_path: Path) -> None:
    """Test that check_repository looks for .erk at repo root, not cwd."""
    # Create subdirectory structure
    repo_root = tmp_path / "repo"
    repo_root.mkdir()
    subdir = repo_root / "src" / "project"
    subdir.mkdir(parents=True)

    # Configure FakeGit so cwd is in a subdirectory but repo root is tmp_path/repo
    git = FakeGit(
        git_common_dirs={subdir: repo_root / ".git"},
        repository_roots={subdir: repo_root},
    )
    ctx = create_test_context(git=git, cwd=subdir)

    # Create .erk at repo root (not in cwd)
    erk_dir = repo_root / ".erk"
    erk_dir.mkdir()

    result = check_repository(ctx)

    # Should find .erk at repo root even though cwd is a subdirectory
    assert result.name == "repository"
    assert result.passed is True
    assert "erk setup detected" in result.message.lower()


# --- Gitignore Tests ---


def test_check_gitignore_entries_no_gitignore(tmp_path: Path) -> None:
    """Test gitignore check when no .gitignore file exists."""
    result = check_gitignore_entries(tmp_path)

    assert result.name == "gitignore"
    assert result.passed is True
    assert "No .gitignore file" in result.message


def test_check_gitignore_entries_all_present(tmp_path: Path) -> None:
    """Test gitignore check when all required entries are present."""
    gitignore = tmp_path / ".gitignore"
    gitignore.write_text("*.pyc\n.erk/scratch/\n.impl/\n", encoding="utf-8")

    result = check_gitignore_entries(tmp_path)

    assert result.name == "gitignore"
    assert result.passed is True
    assert "Required gitignore entries present" in result.message


def test_check_gitignore_entries_missing_scratch(tmp_path: Path) -> None:
    """Test gitignore check when .erk/scratch/ entry is missing."""
    gitignore = tmp_path / ".gitignore"
    gitignore.write_text("*.pyc\n.impl/\n", encoding="utf-8")

    result = check_gitignore_entries(tmp_path)

    assert result.name == "gitignore"
    assert result.passed is False
    assert ".erk/scratch/" in result.message
    assert result.details is not None
    assert "erk init" in result.details


def test_check_gitignore_entries_missing_impl(tmp_path: Path) -> None:
    """Test gitignore check when .impl/ entry is missing."""
    gitignore = tmp_path / ".gitignore"
    gitignore.write_text("*.pyc\n.erk/scratch/\n", encoding="utf-8")

    result = check_gitignore_entries(tmp_path)

    assert result.name == "gitignore"
    assert result.passed is False
    assert ".impl/" in result.message
    assert result.details is not None
    assert "erk init" in result.details


def test_check_gitignore_entries_missing_both(tmp_path: Path) -> None:
    """Test gitignore check when both required entries are missing."""
    gitignore = tmp_path / ".gitignore"
    gitignore.write_text("*.pyc\n__pycache__/\n", encoding="utf-8")

    result = check_gitignore_entries(tmp_path)

    assert result.name == "gitignore"
    assert result.passed is False
    assert ".erk/scratch/" in result.message
    assert ".impl/" in result.message
    assert result.details is not None
    assert "erk init" in result.details


# --- UV Version Check Tests ---


def test_check_uv_version_not_found() -> None:
    """Test check_uv_version when uv is not installed."""
    with patch("erk.core.health_checks.shutil.which", return_value=None):
        result = check_uv_version()

    assert result.name == "uv"
    assert result.passed is False
    assert "not found in PATH" in result.message
    assert result.details is not None
    assert "https://docs.astral.sh/uv" in result.details


def test_check_uv_version_available() -> None:
    """Test check_uv_version when uv is installed."""
    with (
        patch("erk.core.health_checks.shutil.which", return_value="/usr/bin/uv"),
        patch("erk.core.health_checks.subprocess.run") as mock_run,
    ):
        mock_run.return_value.stdout = "uv 0.9.2"
        mock_run.return_value.stderr = ""

        result = check_uv_version()

    assert result.name == "uv"
    assert result.passed is True
    assert "0.9.2" in result.message
    assert result.details is not None
    assert "uv self update" in result.details


def test_check_uv_version_with_build_info() -> None:
    """Test check_uv_version parses version with build info."""
    with (
        patch("erk.core.health_checks.shutil.which", return_value="/usr/bin/uv"),
        patch("erk.core.health_checks.subprocess.run") as mock_run,
    ):
        mock_run.return_value.stdout = "uv 0.9.2 (Homebrew 2025-10-10)"
        mock_run.return_value.stderr = ""

        result = check_uv_version()

    assert result.name == "uv"
    assert result.passed is True
    assert "0.9.2" in result.message
    # Should NOT include the build info in version
    assert "Homebrew" not in result.message


# --- .erk/docs/agent Tests ---


def test_check_docs_agent_no_directory(tmp_path: Path) -> None:
    """Test .erk/docs/agent check when directory doesn't exist."""
    result = check_docs_agent(tmp_path)

    assert result.name == ".erk/docs/agent"
    assert result.passed is True
    assert "No .erk/docs/agent/ directory" in result.message
    assert result.details is not None
    assert "erk init" in result.details


def test_check_docs_agent_all_templates_present(tmp_path: Path) -> None:
    """Test .erk/docs/agent check when all template files exist."""
    docs_agent = tmp_path / ".erk" / "docs" / "agent"
    docs_agent.mkdir(parents=True)

    # Create all expected template files
    (docs_agent / "glossary.md").write_text("# Glossary", encoding="utf-8")
    (docs_agent / "conventions.md").write_text("# Conventions", encoding="utf-8")
    (docs_agent / "guide.md").write_text("# Guide", encoding="utf-8")

    result = check_docs_agent(tmp_path)

    assert result.name == ".erk/docs/agent"
    assert result.passed is True
    assert "Agent documentation templates present" in result.message
    assert result.details is None


def test_check_docs_agent_missing_glossary(tmp_path: Path) -> None:
    """Test .erk/docs/agent check when glossary.md is missing."""
    docs_agent = tmp_path / ".erk" / "docs" / "agent"
    docs_agent.mkdir(parents=True)

    # Create only some template files
    (docs_agent / "conventions.md").write_text("# Conventions", encoding="utf-8")
    (docs_agent / "guide.md").write_text("# Guide", encoding="utf-8")

    result = check_docs_agent(tmp_path)

    assert result.name == ".erk/docs/agent"
    assert result.passed is True  # Info level, not failure
    assert "glossary.md" in result.message
    assert result.details is not None
    assert "erk init --force" in result.details


def test_check_docs_agent_missing_multiple(tmp_path: Path) -> None:
    """Test .erk/docs/agent check when multiple template files are missing."""
    docs_agent = tmp_path / ".erk" / "docs" / "agent"
    docs_agent.mkdir(parents=True)

    # Create only guide.md
    (docs_agent / "guide.md").write_text("# Guide", encoding="utf-8")

    result = check_docs_agent(tmp_path)

    assert result.name == ".erk/docs/agent"
    assert result.passed is True  # Info level, not failure
    assert "glossary.md" in result.message
    assert "conventions.md" in result.message
    assert result.details is not None
    assert "erk init --force" in result.details


def test_check_docs_agent_empty_directory(tmp_path: Path) -> None:
    """Test .erk/docs/agent check when directory exists but is empty."""
    docs_agent = tmp_path / ".erk" / "docs" / "agent"
    docs_agent.mkdir(parents=True)

    result = check_docs_agent(tmp_path)

    assert result.name == ".erk/docs/agent"
    assert result.passed is True  # Info level
    # All three files should be mentioned as missing
    assert "glossary.md" in result.message
    assert "conventions.md" in result.message
    assert "guide.md" in result.message


# --- Hooks Disabled Check Tests ---


def test_check_hooks_disabled_no_files(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when no settings files exist."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)

    result = check_hooks_disabled()

    assert result.name == "claude hooks"
    assert result.passed is True
    assert result.warning is False
    assert "enabled" in result.message.lower()


def test_check_hooks_disabled_in_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when hooks.disabled=true in settings.json."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings = claude_dir / "settings.json"
    settings.write_text(json.dumps({"hooks": {"disabled": True}}), encoding="utf-8")

    result = check_hooks_disabled()

    assert result.name == "claude hooks"
    assert result.passed is True
    assert result.warning is True
    assert "settings.json" in result.message


def test_check_hooks_disabled_in_local(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when hooks.disabled=true in settings.local.json."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings = claude_dir / "settings.local.json"
    settings.write_text(json.dumps({"hooks": {"disabled": True}}), encoding="utf-8")

    result = check_hooks_disabled()

    assert result.name == "claude hooks"
    assert result.passed is True
    assert result.warning is True
    assert "settings.local.json" in result.message


def test_check_hooks_disabled_in_both(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when hooks.disabled=true in both settings files."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    (claude_dir / "settings.json").write_text(
        json.dumps({"hooks": {"disabled": True}}), encoding="utf-8"
    )
    (claude_dir / "settings.local.json").write_text(
        json.dumps({"hooks": {"disabled": True}}), encoding="utf-8"
    )

    result = check_hooks_disabled()

    assert result.name == "claude hooks"
    assert result.passed is True
    assert result.warning is True
    assert "settings.json" in result.message
    assert "settings.local.json" in result.message


def test_check_hooks_disabled_false(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when hooks.disabled=false (explicitly enabled)."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings = claude_dir / "settings.json"
    settings.write_text(json.dumps({"hooks": {"disabled": False}}), encoding="utf-8")

    result = check_hooks_disabled()

    assert result.name == "claude hooks"
    assert result.passed is True
    assert result.warning is False


def test_check_hooks_disabled_no_hooks_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when settings exist but no hooks key."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings = claude_dir / "settings.json"
    settings.write_text(json.dumps({"other_key": "value"}), encoding="utf-8")

    result = check_hooks_disabled()

    assert result.name == "claude hooks"
    assert result.passed is True
    assert result.warning is False


def test_check_hooks_disabled_invalid_json(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Test when settings file has invalid JSON raises error."""
    monkeypatch.setattr(Path, "home", lambda: tmp_path)
    claude_dir = tmp_path / ".claude"
    claude_dir.mkdir()
    settings = claude_dir / "settings.json"
    settings.write_text("{invalid json", encoding="utf-8")

    with pytest.raises(json.JSONDecodeError):
        check_hooks_disabled()


# --- CheckResult warning field tests ---


def test_check_result_with_warning() -> None:
    """Test CheckResult with warning=True."""
    result = CheckResult(
        name="test",
        passed=True,
        message="Test warning",
        warning=True,
    )

    assert result.name == "test"
    assert result.passed is True
    assert result.warning is True
    assert result.message == "Test warning"
    assert result.details is None


def test_check_result_warning_defaults_false() -> None:
    """Test CheckResult warning defaults to False."""
    result = CheckResult(
        name="test",
        passed=True,
        message="Test passed",
    )

    assert result.warning is False


# --- Orphaned Artifacts Tests ---


def test_check_orphaned_artifacts_no_claude_dir(tmp_path: Path) -> None:
    """Test orphaned artifacts check when .claude/ doesn't exist."""
    from erk.core.health_checks import check_orphaned_artifacts

    result = check_orphaned_artifacts(tmp_path)

    assert result.name == "orphaned artifacts"
    assert result.passed is True
    assert result.warning is False
    assert "No .claude/ directory" in result.message


def test_check_orphaned_artifacts_none_found(tmp_path: Path) -> None:
    """Test orphaned artifacts check when no orphans exist."""
    from erk.core.health_checks import check_orphaned_artifacts

    # Create empty .claude directory
    (tmp_path / ".claude").mkdir()

    result = check_orphaned_artifacts(tmp_path)

    assert result.name == "orphaned artifacts"
    assert result.passed is True
    assert result.warning is False
    assert "No orphaned artifacts found" in result.message


def test_check_orphaned_artifacts_found(tmp_path: Path) -> None:
    """Test orphaned artifacts check when orphans are found."""
    from erk.core.health_checks import check_orphaned_artifacts

    # Create orphaned artifact directory (no kits.toml means all are orphaned)
    (tmp_path / ".claude" / "commands" / "old-kit").mkdir(parents=True)

    result = check_orphaned_artifacts(tmp_path)

    assert result.name == "orphaned artifacts"
    assert result.passed is True  # Warning only, doesn't fail
    assert result.warning is True
    assert "1 orphaned artifact(s)" in result.message
    assert result.details is not None
    assert ".claude/commands/old-kit/" in result.details
    assert "rm -r" in result.details


def test_check_orphaned_artifacts_multiple_found(tmp_path: Path) -> None:
    """Test orphaned artifacts check when multiple orphans are found."""
    from erk.core.health_checks import check_orphaned_artifacts

    # Create multiple orphaned artifact directories
    (tmp_path / ".claude" / "commands" / "old-kit").mkdir(parents=True)
    (tmp_path / ".claude" / "agents" / "removed-kit").mkdir(parents=True)

    result = check_orphaned_artifacts(tmp_path)

    assert result.name == "orphaned artifacts"
    assert result.passed is True
    assert result.warning is True
    assert "2 orphaned artifact(s)" in result.message


def test_check_orphaned_artifacts_local_not_orphaned(tmp_path: Path) -> None:
    """Test that .claude/commands/local/ is not considered orphaned."""
    from erk.core.health_checks import check_orphaned_artifacts

    # Create local directory (reserved, should be skipped)
    (tmp_path / ".claude" / "commands" / "local").mkdir(parents=True)

    result = check_orphaned_artifacts(tmp_path)

    assert result.name == "orphaned artifacts"
    assert result.passed is True
    assert result.warning is False
    assert "No orphaned artifacts found" in result.message


# --- Legacy Config Location Tests ---


def test_check_legacy_config_primary_location_exists(tmp_path: Path) -> None:
    """Test legacy config check when primary location (.erk/config.toml) exists."""
    erk_dir = tmp_path / ".erk"
    erk_dir.mkdir()
    (erk_dir / "config.toml").write_text("[env]", encoding="utf-8")

    result = check_legacy_config_locations(tmp_path, None)

    assert result.name == "legacy config"
    assert result.passed is True
    assert result.warning is False
    assert "primary location" in result.message


def test_check_legacy_config_no_legacy_configs(tmp_path: Path) -> None:
    """Test legacy config check when no legacy configs exist."""
    # No configs anywhere
    result = check_legacy_config_locations(tmp_path, None)

    assert result.name == "legacy config"
    assert result.passed is True
    assert result.warning is False
    assert "No legacy config files found" in result.message


def test_check_legacy_config_repo_root_legacy(tmp_path: Path) -> None:
    """Test legacy config check when legacy config at repo root exists."""
    # Create legacy config at repo root
    (tmp_path / "config.toml").write_text("[env]", encoding="utf-8")

    result = check_legacy_config_locations(tmp_path, None)

    assert result.name == "legacy config"
    assert result.passed is True  # Warning only
    assert result.warning is True
    assert "1 legacy config file(s)" in result.message
    assert result.details is not None
    assert "repo root" in result.details
    assert str(tmp_path / ".erk" / "config.toml") in result.details


def test_check_legacy_config_metadata_dir_legacy(tmp_path: Path) -> None:
    """Test legacy config check when legacy config in metadata dir exists."""
    repo_root = tmp_path / "repo"
    repo_root.mkdir()
    metadata_dir = tmp_path / "metadata"
    metadata_dir.mkdir()
    # Create legacy config in metadata dir
    (metadata_dir / "config.toml").write_text("[env]", encoding="utf-8")

    result = check_legacy_config_locations(repo_root, metadata_dir)

    assert result.name == "legacy config"
    assert result.passed is True  # Warning only
    assert result.warning is True
    assert "1 legacy config file(s)" in result.message
    assert result.details is not None
    assert "metadata dir" in result.details


def test_check_legacy_config_both_legacy_locations(tmp_path: Path) -> None:
    """Test legacy config check when both legacy locations have configs."""
    repo_root = tmp_path / "repo"
    repo_root.mkdir()
    metadata_dir = tmp_path / "metadata"
    metadata_dir.mkdir()
    # Create legacy configs in both locations
    (repo_root / "config.toml").write_text("[env]", encoding="utf-8")
    (metadata_dir / "config.toml").write_text("[env]", encoding="utf-8")

    result = check_legacy_config_locations(repo_root, metadata_dir)

    assert result.name == "legacy config"
    assert result.passed is True  # Warning only
    assert result.warning is True
    assert "2 legacy config file(s)" in result.message
    assert result.details is not None
    # Both locations mentioned
    assert "repo root" in result.details
    assert "metadata dir" in result.details


def test_check_legacy_config_ignores_legacy_when_primary_exists(tmp_path: Path) -> None:
    """Test that legacy configs are ignored when primary location exists."""
    # Create primary config
    erk_dir = tmp_path / ".erk"
    erk_dir.mkdir()
    (erk_dir / "config.toml").write_text("[env]", encoding="utf-8")
    # Also create legacy config at repo root
    (tmp_path / "config.toml").write_text("[env]", encoding="utf-8")

    result = check_legacy_config_locations(tmp_path, None)

    # Should report primary location, not warn about legacy
    assert result.name == "legacy config"
    assert result.passed is True
    assert result.warning is False
    assert "primary location" in result.message
