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

import json
import sys
from pathlib import Path

import pytest

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


class TestCLIInit:
    def test_init_python_project(self, tmp_path):
        (tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'\n")
        result = main(["init", str(tmp_path)])
        assert result == 0

        # Verify files created
        assert (tmp_path / ".claude" / "CLAUDE.md").exists()
        assert (tmp_path / ".claude" / ".tokenguard.json").exists()
        assert (tmp_path / ".claude" / "hooks" / "mirror-hook.py").exists()
        assert (tmp_path / ".claude" / "memory" / "MEMORY.md").exists()

    def test_init_javascript_project(self, tmp_path):
        (tmp_path / "package.json").write_text('{"name": "test"}\n')
        result = main(["init", str(tmp_path)])
        assert result == 0

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

    def test_init_with_type_override(self, tmp_path):
        result = main(["init", "--type", "rust", str(tmp_path)])
        assert result == 0

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

    def test_init_no_hooks(self, tmp_path):
        result = main(["init", "--no-hooks", str(tmp_path)])
        assert result == 0

        assert not (tmp_path / ".claude" / "hooks").exists()
        assert not (tmp_path / ".claude" / "settings.json").exists()

    def test_init_dry_run(self, tmp_path):
        result = main(["init", "--dry-run", str(tmp_path)])
        assert result == 0
        assert not (tmp_path / ".claude").exists()

    def test_init_refuses_duplicate(self, tmp_path):
        main(["init", str(tmp_path)])
        result = main(["init", str(tmp_path)])
        assert result == 1  # Should refuse without --force

    def test_init_force_reinitialize(self, tmp_path):
        main(["init", str(tmp_path)])
        result = main(["init", "--force", str(tmp_path)])
        assert result == 0

    def test_init_nonexistent_dir(self, tmp_path):
        result = main(["init", str(tmp_path / "nonexistent")])
        assert result == 1


class TestCLIStatus:
    def test_status_not_initialized(self, tmp_path):
        result = main(["status", str(tmp_path)])
        assert result == 0  # Not an error, just info

    def test_status_initialized(self, tmp_path):
        main(["init", str(tmp_path)])
        result = main(["status", str(tmp_path)])
        assert result == 0


class TestCLIEject:
    def test_eject_removes_tracking(self, tmp_path):
        main(["init", str(tmp_path)])
        assert (tmp_path / ".claude" / ".tokenguard.json").exists()

        result = main(["eject", str(tmp_path)])
        assert result == 0

        # Tracking removed
        assert not (tmp_path / ".claude" / ".tokenguard.json").exists()
        # Files preserved
        assert (tmp_path / ".claude" / "CLAUDE.md").exists()
        assert (tmp_path / ".claude" / "hooks" / "mirror-hook.py").exists()

    def test_eject_not_initialized(self, tmp_path):
        result = main(["eject", str(tmp_path)])
        assert result == 0  # No error, just "nothing to do"


class TestStatusFunction:
    def test_status_returns_none_when_not_init(self, tmp_path):
        assert get_status(tmp_path) is None

    def test_status_returns_info_when_init(self, tmp_path):
        main(["init", "--type", "python", str(tmp_path)])
        status = get_status(tmp_path)
        assert status is not None
        assert status["version"] == "0.2.0"
        assert status["project_type"] == "python"
        assert "claude-md" in status["components"]


class TestEjectFunction:
    def test_eject_cleans_settings(self, tmp_path):
        main(["init", str(tmp_path)])

        # Verify settings has mirror hook
        settings = json.loads((tmp_path / ".claude" / "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(tmp_path)

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


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

    def test_no_updates_when_fresh(self, tmp_path):
        """Freshly initialized project should have no updates."""
        main(["init", str(tmp_path)])
        updates = check_updates(tmp_path)
        assert updates["updatable"] == []
        assert updates["user_modified"] == []
        assert len(updates["current"]) > 0

    def test_detects_missing_file_as_updatable(self, tmp_path):
        """Deleted files should be detected as updatable."""
        main(["init", str(tmp_path)])
        (tmp_path / ".claude" / "CLAUDE.md").unlink()
        updates = check_updates(tmp_path)
        assert ".claude/CLAUDE.md" in updates["updatable"]

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

    def test_detects_template_change_as_updatable(self, tmp_path):
        """Files where template changed but user didn't should be updatable."""
        main(["init", str(tmp_path)])
        # Tamper with the installed hash to simulate a template change
        mirror_file = tmp_path / ".claude" / ".tokenguard.json"
        tracking = json.loads(mirror_file.read_text(encoding="utf-8"))
        # Set installed hash to something different from current content
        tracking["file_hashes"][".claude/CLAUDE.md"] = "0000000000000000"
        mirror_file.write_text(json.dumps(tracking, indent=2), encoding="utf-8")
        # Now the file content won't match the "installed" hash -> user_modified
        # But if we set hash to match current file, but template generates different...
        # Actually, let's test properly: set installed hash to a fake old hash,
        # and also set the file to match that fake hash (impossible to do directly).
        # Simpler: test via check_updates with a file that matches installed hash
        # but template would generate different content.

        # Reset: re-init to get clean state
        main(["init", "--force", str(tmp_path)])
        tracking = json.loads(mirror_file.read_text(encoding="utf-8"))
        # Change the installed hash to "old" value (different from what template generates)
        # but also change the file content to match that old hash
        old_content = "# Old template content\n"
        old_hash = _hash_content(old_content)
        (tmp_path / ".claude" / "CLAUDE.md").write_text(old_content, encoding="utf-8")
        tracking["file_hashes"][".claude/CLAUDE.md"] = old_hash
        mirror_file.write_text(json.dumps(tracking, indent=2), encoding="utf-8")

        updates = check_updates(tmp_path)
        # File matches installed hash, template differs -> updatable
        assert ".claude/CLAUDE.md" in updates["updatable"]

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

    def test_apply_updates_writes_files(self, tmp_path):
        """apply_updates should write template content and update tracking."""
        main(["init", str(tmp_path)])
        # Delete a file to make it updatable
        (tmp_path / ".claude" / "CLAUDE.md").unlink()

        updates = check_updates(tmp_path)
        assert ".claude/CLAUDE.md" in updates["updatable"]

        applied = apply_updates(tmp_path, updates["updatable"])
        assert ".claude/CLAUDE.md" in applied
        assert (tmp_path / ".claude" / "CLAUDE.md").exists()

        # Tracking should be updated
        tracking = json.loads(
            (tmp_path / ".claude" / ".tokenguard.json").read_text(encoding="utf-8")
        )
        content = (tmp_path / ".claude" / "CLAUDE.md").read_text(encoding="utf-8")
        assert tracking["file_hashes"][".claude/CLAUDE.md"] == _hash_content(content)

    def test_update_cli_no_force_shows_list(self, tmp_path, capsys):
        """update without --force should just list updatable files."""
        main(["init", str(tmp_path)])
        (tmp_path / ".claude" / "CLAUDE.md").unlink()
        result = main(["update", str(tmp_path)])
        assert result == 0

    def test_update_cli_force_applies(self, tmp_path):
        """update --force should apply updates."""
        main(["init", str(tmp_path)])
        (tmp_path / ".claude" / "CLAUDE.md").unlink()
        result = main(["update", "--force", str(tmp_path)])
        assert result == 0
        assert (tmp_path / ".claude" / "CLAUDE.md").exists()

    def test_update_not_initialized(self, tmp_path):
        """update on non-initialized project should error."""
        result = main(["update", str(tmp_path)])
        assert result == 1


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

    def test_skips_hooks_when_grip_detected(self, tmp_path):
        """When GRIP hook is registered, mirror hooks should be skipped."""
        # Pre-create a settings.json with GRIP hook
        claude_dir = tmp_path / ".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 = main(["init", "--force", str(tmp_path)])
        assert result == 0

        # Mirror hook should NOT be installed
        assert not (tmp_path / ".claude" / "hooks" / "mirror-hook.py").exists()

    def test_standalone_overrides_grip_detection(self, tmp_path):
        """--standalone should install hooks even if GRIP detected."""
        claude_dir = tmp_path / ".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 = main(["init", "--force", "--standalone", str(tmp_path)])
        assert result == 0

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


class TestCLINoCommand:
    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
