"""Tests for project context: file readers, config, skills sync, gitignore."""

import os
import subprocess
from pathlib import Path
from unittest.mock import patch

import project


# ═══════════════════════════════════════════════════════════════════════════
# File readers
# ═══════════════════════════════════════════════════════════════════════════


class TestReadFileText:
    def test_reads_file(self, tmp_path: Path):
        f = tmp_path / "test.txt"
        f.write_text("  hello  ")
        assert project.read_file_text(f) == "hello"

    def test_missing_returns_empty(self, tmp_path: Path):
        assert project.read_file_text(tmp_path / "nope.txt") == ""


class TestConsumeBacklog:
    def test_consumes_and_deletes(self, tmp_path: Path):
        bl = tmp_path / "backlog.md"
        bl.write_text("Fix the login page")
        result = project.consume_backlog(bl)
        assert result == "Fix the login page"
        assert not bl.exists()

    def test_missing_returns_empty(self, tmp_path: Path):
        assert project.consume_backlog(tmp_path / "backlog.md") == ""


class TestParseEnvVarNames:
    def test_parses_env_keys(self, tmp_path: Path):
        f = tmp_path / ".env"
        f.write_text("API_KEY=secret\n# comment\nDB_URL=postgres://...\n")
        result = project.parse_env_var_names(f)
        assert len(result) == 2
        assert any("API_KEY" in r for r in result)
        assert any("DB_URL" in r for r in result)

    def test_missing_returns_empty(self, tmp_path: Path):
        assert project.parse_env_var_names(tmp_path / ".env") == []


# ═══════════════════════════════════════════════════════════════════════════
# Per-project config
# ═══════════════════════════════════════════════════════════════════════════


class TestLoadProjectConfig:
    def test_defaults(self, project_dir: Path):
        cfg = project.load_project_config(project_dir)
        assert cfg["no_push"] is False

    def test_reads_config_yaml(self, project_dir: Path):
        (project_dir / ".autobuilder" / "config.yaml").write_text(
            "no_push: true\n"
        )
        cfg = project.load_project_config(project_dir)
        assert cfg["no_push"] is True

    def test_ignores_unknown_keys(self, project_dir: Path):
        (project_dir / ".autobuilder" / "config.yaml").write_text(
            "unknown_key: whatever\nno_push: false\n"
        )
        cfg = project.load_project_config(project_dir)
        assert "unknown_key" not in cfg

    def test_comments_and_blanks(self, project_dir: Path):
        (project_dir / ".autobuilder" / "config.yaml").write_text(
            "# comment\n\nno_push: true\n"
        )
        cfg = project.load_project_config(project_dir)
        assert cfg["no_push"] is True


# ═══════════════════════════════════════════════════════════════════════════
# Skills sync
# ═══════════════════════════════════════════════════════════════════════════


class TestSyncSkillsToProject:
    def test_creates_single_symlink(self, project_dir: Path):
        result = project.sync_skills_to_project(project_dir)
        link = project_dir / ".cursor" / "skills" / "autobuilder"
        assert result is True
        assert link.is_symlink()
        assert (link / "build-feature" / "SKILL.md").exists()

    def test_idempotent(self, project_dir: Path):
        project.sync_skills_to_project(project_dir)
        result = project.sync_skills_to_project(project_dir)
        assert result is True
        link = project_dir / ".cursor" / "skills" / "autobuilder"
        assert link.is_symlink()


class TestSetupProjectAgentsMd:
    def test_creates_symlink(self, project_dir: Path):
        result = project.setup_project_agents_md(project_dir)
        assert result is True
        agents_md = project_dir / "AGENTS.md"
        assert agents_md.is_symlink()


class TestEnsureGitignoreEntries:
    def test_adds_entries(self, project_dir: Path):
        project.ensure_gitignore_entries(
            project_dir, [".cursor/skills/autobuilder", "*.log"]
        )
        content = (project_dir / ".gitignore").read_text()
        assert ".cursor/skills/autobuilder" in content
        assert "*.log" in content

    def test_idempotent(self, project_dir: Path):
        project.ensure_gitignore_entries(project_dir, [".cursor/skills/autobuilder"])
        project.ensure_gitignore_entries(project_dir, [".cursor/skills/autobuilder"])
        content = (project_dir / ".gitignore").read_text()
        assert content.count(".cursor/skills/autobuilder") == 1


class TestUntrackIgnoredFiles:
    def test_skips_non_git_directory(self, project_dir: Path):
        """No .git dir → no subprocess calls."""
        with patch("project.subprocess.run") as mock_run:
            project._untrack_ignored_files(project_dir, [".autobuilder/status.md"])
        mock_run.assert_not_called()

    def test_calls_git_rm_cached(self, project_dir: Path):
        (project_dir / ".git").mkdir()
        with patch("project.subprocess.run") as mock_run:
            project._untrack_ignored_files(
                project_dir,
                [".autobuilder/status.md", ".autobuilder/daemon.pid"],
            )
        assert mock_run.call_count == 2
        args_0 = mock_run.call_args_list[0][0][0]
        assert args_0 == [
            "git", "rm", "-r", "--cached", "--quiet",
            "--ignore-unmatch", ".autobuilder/status.md",
        ]

    def test_resolves_double_star_dir_glob(self, project_dir: Path):
        (project_dir / ".git").mkdir()
        ss_dir = project_dir / "a" / "b" / "review-screenshots"
        ss_dir.mkdir(parents=True)
        with patch("project.subprocess.run") as mock_run:
            project._untrack_ignored_files(
                project_dir, ["**/review-screenshots/"],
            )
        assert mock_run.call_count == 1
        args = mock_run.call_args_list[0][0][0]
        assert "a/b/review-screenshots" in args

    def test_skips_wildcard_entries(self, project_dir: Path):
        (project_dir / ".git").mkdir()
        with patch("project.subprocess.run") as mock_run:
            project._untrack_ignored_files(
                project_dir, ["*.log", ".autobuilder/loop.log"],
            )
        assert mock_run.call_count == 1
        args = mock_run.call_args_list[0][0][0]
        assert ".autobuilder/loop.log" in args

    def test_handles_subprocess_error(self, project_dir: Path):
        (project_dir / ".git").mkdir()
        with patch("project.subprocess.run", side_effect=OSError("fail")):
            project._untrack_ignored_files(
                project_dir, [".autobuilder/status.md"],
            )
