"""Tests for ``remove_path_from_shell_configs`` — the uninstaller's shell-rc rewrite.

This rewrites files Hermes does not own (``~/.bashrc``, ``~/.zshrc``, ...) and
takes no backup of them, so the rewrite has to be atomic: a bare
``write_text()`` truncates the rc file before the new content lands, and the
caller wraps everything in ``except Exception: log_warn(...)``, so a partial
write is downgraded to a warning and the user's next login starts a bare shell.
"""
from __future__ import annotations

import os
import stat
import sys
from pathlib import Path

import pytest

from hermes_cli import uninstall


ZSHRC = (
    "export EDITOR=vim\n"
    "alias ll='ls -la'\n"
    "\n"
    "# Hermes Agent\n"
    'export PATH="$HOME/.local/bin:$PATH"\n'
    "\n"
    "source ~/.work-profile\n"
)


@pytest.fixture
def fake_home(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Point both ``Path.home()`` and ``HERMES_HOME`` at a throwaway dir."""
    home = tmp_path / "home"
    home.mkdir()
    monkeypatch.setattr(Path, "home", classmethod(lambda cls: home))
    monkeypatch.setenv("HERMES_HOME", str(home / ".hermes"))
    return home


class TestHappyPath:
    def test_hermes_path_block_is_removed(self, fake_home: Path):
        rc = fake_home / ".zshrc"
        rc.write_text(ZSHRC, encoding="utf-8")

        removed = uninstall.remove_path_from_shell_configs()

        assert removed == [rc]
        text = rc.read_text(encoding="utf-8")
        assert "# Hermes Agent" not in text
        # The user's own lines are untouched.
        assert "export EDITOR=vim" in text
        assert "source ~/.work-profile" in text

    def test_untouched_rc_is_not_reported(self, fake_home: Path):
        rc = fake_home / ".zshrc"
        rc.write_text("export EDITOR=vim\n", encoding="utf-8")

        assert uninstall.remove_path_from_shell_configs() == []
        assert rc.read_text(encoding="utf-8") == "export EDITOR=vim\n"


class TestCrashDurability:
    def test_shell_config_survives_an_interrupted_rewrite(self, fake_home: Path):
        """An interrupted rewrite must leave the rc file byte-identical.

        There is no backup of the user's shell rc anywhere in this code path,
        so a truncated write is unrecoverable.
        """
        rc = fake_home / ".zshrc"
        rc.write_text(ZSHRC, encoding="utf-8")
        original = rc.read_bytes()

        def boom(fd):
            raise OSError("simulated crash mid-write")

        # Scoped context so restoring os.fsync doesn't also undo the
        # Path.home()/HERMES_HOME patches the fake_home fixture installed.
        with pytest.MonkeyPatch.context() as mp:
            mp.setattr(os, "fsync", boom)
            removed = uninstall.remove_path_from_shell_configs()

        # The write failed, so the rc must not be reported as modified...
        assert removed == []
        # ...and it must still be exactly what the user had.
        assert rc.read_bytes() == original
        # The aborted write must not leave a temp file behind in $HOME.
        assert list(fake_home.glob("*.tmp")) == []

    def test_symlinked_shell_config_stays_a_symlink(self, fake_home: Path):
        """A dotfiles-repo ``~/.zshrc`` is a symlink; replacing it with a
        regular file silently detaches the user's dotfiles."""
        dotfiles = fake_home / "dotfiles"
        dotfiles.mkdir()
        real = dotfiles / "zshrc"
        real.write_text(ZSHRC, encoding="utf-8")
        rc = fake_home / ".zshrc"
        rc.symlink_to(real)

        removed = uninstall.remove_path_from_shell_configs()

        assert removed == [rc]
        assert rc.is_symlink(), "the symlink was replaced by a regular file"
        assert "# Hermes Agent" not in real.read_text(encoding="utf-8")
        assert "export EDITOR=vim" in real.read_text(encoding="utf-8")

    @pytest.mark.skipif(sys.platform == "win32", reason="POSIX permission bits")
    def test_existing_file_mode_is_preserved(self, fake_home: Path):
        """Shell rc files are normally 0644; uninstalling must not change that."""
        rc = fake_home / ".zshrc"
        rc.write_text(ZSHRC, encoding="utf-8")
        os.chmod(rc, 0o644)

        uninstall.remove_path_from_shell_configs()

        mode = stat.S_IMODE(rc.stat().st_mode)
        assert mode == 0o644, f"mode changed to {oct(mode)}"
