"""Tests for mureo setup commands (install_skills).

Phase 3 (PR #77) merged slash commands into skills, so ``install_commands``
no longer exists. The 10 former commands (daily-check, budget-rebalance,
search-term-cleanup, creative-refresh, rescue, goal-review, weekly-report,
competitive-scan, onboard, sync-state) now ship as their own SKILL.md
directories alongside the foundation skills (_mureo-shared, _mureo-strategy,
_mureo-google-ads, _mureo-meta-ads, _mureo-learning). Total packaged: 15.
"""

from __future__ import annotations

from typing import TYPE_CHECKING
from unittest.mock import patch

import pytest

if TYPE_CHECKING:
    from pathlib import Path


# Number of skills shipped in mureo/_data/skills/. Update this constant
# whenever a skill is added or removed from the packaged set.
EXPECTED_PACKAGED_SKILLS = 16


@pytest.mark.unit
def test_install_skills(tmp_path: Path) -> None:
    """install_skills copies all skill directories to target.

    Includes both the operational skills migrated from commands
    (daily-check, onboard, rescue, etc.) and the foundation skills
    (_mureo-shared, _mureo-strategy, etc.).
    """
    from mureo.cli.setup_cmd import install_skills

    count, dest = install_skills(target_dir=tmp_path / "skills")

    assert count == EXPECTED_PACKAGED_SKILLS
    assert dest == tmp_path / "skills"
    # Operational skills (formerly slash commands)
    assert (dest / "daily-check" / "SKILL.md").exists()
    assert (dest / "learn" / "SKILL.md").exists()
    assert (dest / "onboard" / "SKILL.md").exists()
    assert (dest / "rescue" / "SKILL.md").exists()
    # Foundation skills (referenced as PREREQUISITE by the operational ones)
    assert (dest / "_mureo-shared" / "SKILL.md").exists()
    assert (dest / "_mureo-strategy" / "SKILL.md").exists()
    assert (dest / "_mureo-google-ads" / "SKILL.md").exists()
    assert (dest / "_mureo-meta-ads" / "SKILL.md").exists()
    assert (dest / "_mureo-learning" / "SKILL.md").exists()


@pytest.mark.unit
def test_install_skills_creates_directory(tmp_path: Path) -> None:
    """install_skills creates target directory if it doesn't exist."""
    from mureo.cli.setup_cmd import install_skills

    target = tmp_path / "deep" / "nested" / "skills"
    count, dest = install_skills(target_dir=target)

    assert count == EXPECTED_PACKAGED_SKILLS
    assert dest.exists()


@pytest.mark.unit
def test_install_skills_overwrites_existing(tmp_path: Path) -> None:
    """install_skills replaces existing skill directories (idempotent)."""
    from mureo.cli.setup_cmd import install_skills

    target = tmp_path / "skills"
    target.mkdir()
    old_skill = target / "_mureo-shared"
    old_skill.mkdir()
    (old_skill / "SKILL.md").write_text("old content")

    install_skills(target_dir=target)

    content = (target / "_mureo-shared" / "SKILL.md").read_text()
    assert content != "old content"


@pytest.mark.unit
def test_install_skills_preserves_extra_skills(tmp_path: Path) -> None:
    """install_skills does not delete extra skill directories."""
    from mureo.cli.setup_cmd import install_skills

    target = tmp_path / "skills"
    target.mkdir()
    custom = target / "my-custom-skill"
    custom.mkdir()
    (custom / "SKILL.md").write_text("custom")

    install_skills(target_dir=target)

    assert (target / "my-custom-skill" / "SKILL.md").read_text() == "custom"


@pytest.mark.unit
def test_install_skills_replaces_symlink_without_touching_target(
    tmp_path: Path,
) -> None:
    """A symlink in the destination must be replaced with a real copy.

    Developers often symlink `~/.claude/skills/<bundled>/` at their repo's
    dev copy. Re-running `mureo setup claude-code` used to crash with
    ``OSError: Cannot call rmtree on a symbolic link`` because
    ``shutil.rmtree`` refuses symlinks by design (to avoid blowing away
    the link's target). This regression pins the corrected behavior:
    the symlink itself is removed, then a real copy of the bundled skill
    lands in its place. The external target the symlink used to point
    at is left untouched.
    """
    from mureo.cli.setup_cmd import install_skills

    external_target = tmp_path / "external" / "_mureo-shared"
    external_target.mkdir(parents=True)
    (external_target / "SKILL.md").write_text("dev-link content")
    (external_target / "precious.txt").write_text("do not delete")

    target = tmp_path / "skills"
    target.mkdir()
    link_path = target / "_mureo-shared"
    link_path.symlink_to(external_target, target_is_directory=True)

    install_skills(target_dir=target)

    assert not link_path.is_symlink()
    assert link_path.is_dir()
    assert (link_path / "SKILL.md").exists()
    assert external_target.exists()
    assert (external_target / "SKILL.md").read_text() == "dev-link content"
    assert (external_target / "precious.txt").read_text() == "do not delete"


@pytest.mark.unit
def test_install_skills_default_path(tmp_path: Path) -> None:
    """install_skills uses ~/.claude/skills as default."""
    from mureo.cli.setup_cmd import install_skills

    with patch("mureo.cli.setup_cmd.Path.home", return_value=tmp_path):
        count, dest = install_skills()

    assert dest == tmp_path / ".claude" / "skills"
    assert count == EXPECTED_PACKAGED_SKILLS


# ---------------------------------------------------------------------------
# Non-interactive behavior — regression tests for TTY-safe setup commands
# ---------------------------------------------------------------------------


@pytest.mark.unit
def test_should_skip_auth_when_flag_true() -> None:
    """--skip-auth always wins regardless of TTY state."""
    from mureo.cli.setup_cmd import _should_skip_auth

    skip, banner = _should_skip_auth(skip_auth_flag=True)
    assert skip is True
    assert banner is not None
    assert "--skip-auth" in banner


@pytest.mark.unit
def test_should_skip_auth_when_no_tty(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Missing TTY (AI-agent subprocess, CI) auto-implies skip-auth."""
    from mureo.cli.setup_cmd import _should_skip_auth

    monkeypatch.setattr("mureo.cli.setup_cmd.is_tty", lambda: False)
    skip, banner = _should_skip_auth(skip_auth_flag=False)
    assert skip is True
    assert banner is not None
    assert "No TTY" in banner
    assert "mureo auth setup" in banner


@pytest.mark.unit
def test_should_not_skip_auth_when_tty_and_no_flag(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Interactive TTY preserves the existing prompting flow."""
    from mureo.cli.setup_cmd import _should_skip_auth

    monkeypatch.setattr("mureo.cli.setup_cmd.is_tty", lambda: True)
    skip, banner = _should_skip_auth(skip_auth_flag=False)
    assert skip is False
    assert banner is None


@pytest.mark.unit
def test_setup_claude_code_runs_without_tty(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    """`mureo setup claude-code` must complete non-interactively under
    an AI agent (no TTY) AND still perform every non-auth install step.

    Regression lock: a future refactor that silently no-ops everything
    in non-TTY mode would still print "Setup complete" — so we also
    assert the MCP config file, the credential guard hook, the workflow
    commands directory, and the skills directory were all actually
    created on disk.
    """
    import json

    import typer.testing

    from mureo.cli.main import app

    monkeypatch.setattr("mureo.cli.setup_cmd.is_tty", lambda: False)
    monkeypatch.setattr("mureo.cli.setup_cmd.Path.home", lambda: tmp_path)
    monkeypatch.setattr("mureo.auth_setup.Path.home", lambda: tmp_path)
    monkeypatch.setattr("mureo.cli.setup_codex.Path.home", lambda: tmp_path)
    # Force the no-CLI fallback so the test never shells out to a real
    # ``claude`` binary (which would mutate the developer's real
    # ~/.claude.json); the mureo MCP block then lands in tmp/.claude.json.
    monkeypatch.setattr("mureo.auth_setup.shutil.which", lambda _: None)
    # Guard: if any code path tries to reach OAuth, fail loudly.
    from mureo import auth_setup

    def _boom(*_: object, **__: object) -> None:
        raise AssertionError("OAuth must not be invoked in non-TTY mode")

    monkeypatch.setattr(auth_setup, "setup_google_ads", _boom)
    monkeypatch.setattr(auth_setup, "setup_meta_ads", _boom)

    runner = typer.testing.CliRunner()
    result = runner.invoke(app, ["setup", "claude-code"])
    assert result.exit_code == 0, result.output
    assert "No TTY detected" in result.output
    assert "Setup complete" in result.output

    # MCP config must exist with mureo registered. User-scope MCP servers
    # live in ~/.claude.json (NOT settings.json — never read for MCP).
    claude_json = tmp_path / ".claude.json"
    assert claude_json.exists()
    mcp_cfg = json.loads(claude_json.read_text(encoding="utf-8"))
    assert "mureo" in mcp_cfg.get("mcpServers", {})
    # Credential guard hook installed in settings.json (hooks DO live
    # there — only MCP discovery does not).
    settings_path = tmp_path / ".claude" / "settings.json"
    assert settings_path.exists()
    settings = json.loads(settings_path.read_text(encoding="utf-8"))
    hooks = settings.get("hooks", {}).get("PreToolUse", [])
    assert any(
        "[mureo-credential-guard]" in h.get("command", "")
        for entry in hooks
        for h in entry.get("hooks", [])
    )
    # Skills were copied — both operational (onboard, daily-check) and
    # foundation (_mureo-shared). Phase 3 retired the separate commands
    # directory; commands now live as skills.
    assert (tmp_path / ".claude" / "skills" / "onboard" / "SKILL.md").exists()
    assert (tmp_path / ".claude" / "skills" / "daily-check" / "SKILL.md").exists()
    assert (tmp_path / ".claude" / "skills" / "_mureo-shared" / "SKILL.md").exists()
