"""Integration tests for TokenGuard CLI."""

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

import pytest

from tokenguard.cli import main
from tokenguard.scaffold import _hash_content, scaffold_project
from tokenguard.updater import apply_updates, check_updates, eject, get_status


@pytest.fixture
def project_env(tmp_path):
    """Create isolated project and user dirs for testing."""
    project_dir = tmp_path / "project"
    project_dir.mkdir()
    user_dir = tmp_path / "user_claude"
    user_dir.mkdir()
    return project_dir, user_dir


def _init_project(project_dir, user_dir, extra_args=None):
    """Helper: run scaffold_project directly for test isolation."""
    args = extra_args or {}
    return scaffold_project(
        project_dir=project_dir,
        project_type=args.get("type", "generic"),
        standalone=args.get("standalone", True),
        no_hooks=args.get("no_hooks", False),
        dry_run=args.get("dry_run", False),
        user_claude_dir=user_dir,
    )


class TestScaffoldInit:
    def test_init_python_project(self, project_env):
        project_dir, user_dir = project_env
        (project_dir / "pyproject.toml").write_text("[project]\nname = 'test'\n")

        result = _init_project(project_dir, user_dir, {"type": "python"})

        # Project-level
        assert (project_dir / ".claude" / "CLAUDE.md").exists()
        assert (project_dir / ".claude" / ".tokenguard.json").exists()
        # User-level
        assert (user_dir / "hooks" / "mirror-hook.py").exists()
        assert (user_dir / "memory" / "MEMORY.md").exists()
        assert (user_dir / "rules" / "efficiency-rules.md").exists()
        assert (user_dir / "settings.json").exists()

    def test_init_javascript_project(self, project_env):
        project_dir, user_dir = project_env
        (project_dir / "package.json").write_text('{"name": "test"}\n')

        _init_project(project_dir, user_dir, {"type": "javascript"})

        claude_md = (project_dir / ".claude" / "CLAUDE.md").read_text()
        assert "javascript" in claude_md.lower()

    def test_init_with_type_override(self, project_env):
        project_dir, user_dir = project_env

        _init_project(project_dir, user_dir, {"type": "rust"})

        mirror = json.loads((project_dir / ".claude" / ".tokenguard.json").read_text())
        assert mirror["project_type"] == "rust"

    def test_init_no_hooks(self, project_env):
        project_dir, user_dir = project_env

        _init_project(project_dir, user_dir, {"no_hooks": True})

        assert not (user_dir / "hooks").exists()
        assert not (user_dir / "settings.json").exists()

    def test_init_dry_run(self, project_env):
        project_dir, user_dir = project_env

        _init_project(project_dir, user_dir, {"dry_run": True})

        assert not (project_dir / ".claude").exists()
        assert not (user_dir / "rules").exists()

    def test_init_second_project_skips_existing_user_files(self, project_env):
        """Second project init should skip user-level files that already exist."""
        project_dir, user_dir = project_env
        project_dir2 = project_dir.parent / "project2"
        project_dir2.mkdir()

        # Init first project
        _init_project(project_dir, user_dir, {"type": "python"})
        # Init second project — user files already exist
        result = _init_project(project_dir2, user_dir, {"type": "javascript"})

        # User files should be skipped
        assert len(result["skipped_existing"]) > 0
        # But project CLAUDE.md should be different
        assert (project_dir2 / ".claude" / "CLAUDE.md").exists()


class TestStatus:
    def test_status_not_initialized(self, project_env):
        project_dir, _ = project_env
        assert get_status(project_dir) is None

    def test_status_initialized(self, project_env):
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir, {"type": "python"})
        status = get_status(project_dir)
        assert status is not None
        assert status["version"] == "0.3.0"
        assert status["project_type"] == "python"
        assert "claude-md" in status["components"]


class TestEject:
    def test_eject_removes_tracking(self, project_env):
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        assert (project_dir / ".claude" / ".tokenguard.json").exists()

        actions = eject(project_dir, user_claude_dir=user_dir)

        # Tracking removed
        assert not (project_dir / ".claude" / ".tokenguard.json").exists()
        # Project files preserved
        assert (project_dir / ".claude" / "CLAUDE.md").exists()
        # User files preserved
        assert (user_dir / "hooks" / "mirror-hook.py").exists()

    def test_eject_cleans_settings(self, project_env):
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)

        # Verify settings has mirror hook
        settings = json.loads((user_dir / "settings.json").read_text())
        assert any(
            "mirror-hook" in h.get("command", "")
            for group in settings["hooks"]["PreToolUse"]
            for h in group.get("hooks", [])
        )

        eject(project_dir, user_claude_dir=user_dir)

        # Settings should have mirror hook removed
        settings = json.loads((user_dir / "settings.json").read_text())
        for group in settings["hooks"].get("PreToolUse", []):
            for h in group.get("hooks", []):
                assert "mirror-hook" not in h.get("command", "")

    def test_eject_not_initialized(self, project_env):
        project_dir, user_dir = project_env
        actions = eject(project_dir, user_claude_dir=user_dir)
        assert any("No TokenGuard tracking" in a for a in actions)


class TestUpdateTemplateDiff:
    """Tests for template re-diffing in check_updates."""

    def test_no_updates_when_fresh(self, project_env):
        """Freshly initialized project should have no updates."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        updates = check_updates(project_dir, user_claude_dir=user_dir)
        assert updates["updatable"] == []
        assert updates["user_modified"] == []
        assert len(updates["current"]) > 0

    def test_detects_missing_project_file_as_updatable(self, project_env):
        """Deleted project files should be detected as updatable."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        (project_dir / ".claude" / "CLAUDE.md").unlink()
        updates = check_updates(project_dir, user_claude_dir=user_dir)
        updatable_paths = [rel for _, rel in updates["updatable"]]
        assert ".claude/CLAUDE.md" in updatable_paths

    def test_detects_missing_user_file_as_updatable(self, project_env):
        """Deleted user-level files should be detected as updatable."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        (user_dir / "rules" / "efficiency-rules.md").unlink()
        updates = check_updates(project_dir, user_claude_dir=user_dir)
        updatable_paths = [rel for _, rel in updates["updatable"]]
        assert "rules/efficiency-rules.md" in updatable_paths

    def test_detects_user_modified_file(self, project_env):
        """User-modified files should be flagged, not overwritten."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        claude_md = project_dir / ".claude" / "CLAUDE.md"
        claude_md.write_text("# My custom CLAUDE.md\n", encoding="utf-8")
        updates = check_updates(project_dir, user_claude_dir=user_dir)
        assert ".claude/CLAUDE.md" in updates["user_modified"]

    def test_detects_template_change_as_updatable(self, project_env):
        """Files where template changed but user didn't should be updatable."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)

        # Simulate: set installed hash to old value, file matches old hash
        mirror_file = project_dir / ".claude" / ".tokenguard.json"
        tracking = json.loads(mirror_file.read_text(encoding="utf-8"))
        old_content = "# Old template content\n"
        old_hash = _hash_content(old_content)
        (project_dir / ".claude" / "CLAUDE.md").write_text(old_content, encoding="utf-8")
        tracking["project_file_hashes"][".claude/CLAUDE.md"] = old_hash
        mirror_file.write_text(json.dumps(tracking, indent=2), encoding="utf-8")

        updates = check_updates(project_dir, user_claude_dir=user_dir)
        updatable_paths = [rel for _, rel in updates["updatable"]]
        assert ".claude/CLAUDE.md" in updatable_paths

    def test_missing_tracking_returns_flag(self, project_env):
        """Missing .tokenguard.json should set missing_tracking."""
        project_dir, user_dir = project_env
        updates = check_updates(project_dir, user_claude_dir=user_dir)
        assert updates["missing_tracking"] is True

    def test_apply_updates_writes_project_file(self, project_env):
        """apply_updates should write template content and update tracking."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        (project_dir / ".claude" / "CLAUDE.md").unlink()

        updates = check_updates(project_dir, user_claude_dir=user_dir)
        updatable = updates["updatable"]
        assert any(rel == ".claude/CLAUDE.md" for _, rel in updatable)

        applied = apply_updates(project_dir, updatable, user_claude_dir=user_dir)
        assert any(".claude/CLAUDE.md" in p for p in applied)
        assert (project_dir / ".claude" / "CLAUDE.md").exists()

    def test_apply_updates_writes_user_file(self, project_env):
        """apply_updates should restore deleted user-level files."""
        project_dir, user_dir = project_env
        _init_project(project_dir, user_dir)
        (user_dir / "rules" / "efficiency-rules.md").unlink()

        updates = check_updates(project_dir, user_claude_dir=user_dir)
        applied = apply_updates(project_dir, updates["updatable"], user_claude_dir=user_dir)
        assert any("efficiency-rules.md" in p for p in applied)
        assert (user_dir / "rules" / "efficiency-rules.md").exists()


class TestGRIPCoexistence:
    """Tests for GRIP hook coexistence."""

    def test_skips_hooks_when_grip_detected(self, project_env):
        """When GRIP hook is registered, mirror hooks should be skipped."""
        project_dir, user_dir = project_env
        # Pre-create a settings.json with GRIP hook at project level
        claude_dir = project_dir / ".claude"
        claude_dir.mkdir()
        settings = {
            "hooks": {
                "PreToolUse": [{
                    "matcher": ".*",
                    "hooks": [{
                        "type": "command",
                        "command": "python3 ~/.claude/hooks/context-gate-hook.py"
                    }]
                }]
            }
        }
        (claude_dir / "settings.json").write_text(json.dumps(settings))

        # standalone=False so GRIP detection isn't bypassed
        result = _init_project(project_dir, user_dir, {"standalone": False})

        # Mirror hook should NOT be installed
        assert "hooks/mirror-hook.py" not in result["user_files"]

    def test_standalone_overrides_grip_detection(self, project_env):
        """--standalone should install hooks even if GRIP detected."""
        project_dir, user_dir = project_env
        claude_dir = project_dir / ".claude"
        claude_dir.mkdir()
        settings = {
            "hooks": {
                "PreToolUse": [{
                    "matcher": ".*",
                    "hooks": [{
                        "type": "command",
                        "command": "python3 ~/.claude/hooks/context-gate-hook.py"
                    }]
                }]
            }
        }
        (claude_dir / "settings.json").write_text(json.dumps(settings))

        result = _init_project(project_dir, user_dir, {"standalone": True})

        # Mirror hook SHOULD be installed due to --standalone
        assert (user_dir / "hooks" / "mirror-hook.py").exists()


class TestCLIEntryPoints:
    """Tests for CLI arg parsing and help."""

    def test_no_command_shows_help(self):
        result = main([])
        assert result == 0

    def test_version(self, capsys):
        with pytest.raises(SystemExit) as exc_info:
            main(["--version"])
        assert exc_info.value.code == 0
