"""Edge-case tests for per-venv session config.

Covers scenarios not in test_config.py:
- Corrupted venv config (bad JSON)
- v0.1.2 upgrade path: global config exists, no per-venv, VIRTUAL_ENV set
- clear_remote_config via VIRTUAL_ENV (no explicit venv_path arg)
- save_remote_config without venv_path (global only)
- Uninstall flow: restore_venv deletes marker, then clear_remote_config
- Wrapper template includes VIRTUAL_ENV export
- clear_remote_config with non-existent venv path
"""

from __future__ import annotations

import json
import os
import stat
from pathlib import Path

import pytest

from specter_cli.config import (
    clear_remote_config,
    has_active_session,
    load_remote_config,
    save_remote_config,
)


@pytest.fixture(autouse=True)
def _isolated_config(tmp_path, monkeypatch):
    """Redirect all config files to a temp directory via SPECTER_HOME."""
    sd = tmp_path / ".specter"
    sd.mkdir()
    monkeypatch.setenv("SPECTER_HOME", str(sd))


class TestPerVenvEdgeCases:
    """Edge cases for per-venv config resolution."""

    def test_corrupted_venv_config_falls_back_to_global(
        self, tmp_path, monkeypatch
    ):
        """Corrupted .specter.json in venv should fall through to global."""
        venv = tmp_path / "myvenv"
        venv.mkdir()
        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text("not valid json{{{")

        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))
        config = load_remote_config()
        assert config["session_id"] == "sess_global"

    def test_v012_upgrade_path_global_only(self, tmp_path, monkeypatch):
        """v0.1.2 user: global remote.json exists, no per-venv config.

        When VIRTUAL_ENV is set but the venv has no .specter.json,
        load_remote_config should fall through to the global config.
        This simulates the upgrade path where existing users have a
        global session but haven't re-installed with the new code.
        """
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # No .specter.json in venv — just global config.
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_legacy",
            gpu_type="h100",
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))
        config = load_remote_config()
        assert config["session_id"] == "sess_legacy"
        assert has_active_session()

    def test_clear_via_virtual_env_not_explicit_path(
        self, tmp_path, monkeypatch
    ):
        """clear_remote_config without venv_path uses VIRTUAL_ENV."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(json.dumps({
            "patched": True,
            "gpu_type": "h100",
            "host": "10.0.0.1",
            "user": "root",
            "session_id": "sess_abc",
            "ssh_port": 22,
        }))

        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_abc",
            gpu_type="h100",
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))
        # Call without explicit venv_path — should use VIRTUAL_ENV.
        clear_remote_config()

        # Venv config should keep marker, lose session.
        venv_data = json.loads(venv_cfg.read_text())
        assert venv_data.get("patched") is True
        assert "session_id" not in venv_data
        assert "host" not in venv_data

    def test_save_without_venv_path_global_only(self, tmp_path, monkeypatch):
        """save_remote_config without venv_path only writes global."""
        venv = tmp_path / "myvenv"
        venv.mkdir()
        monkeypatch.setenv("VIRTUAL_ENV", str(venv))

        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_abc",
            gpu_type="h100",
            # No venv_path kwarg.
        )

        # Global should exist.
        from specter_cli.config import remote_file
        assert remote_file().is_file()

        # Venv config should NOT be written (no venv_path passed).
        venv_cfg = venv / ".specter.json"
        assert not venv_cfg.exists()

    def test_clear_after_restore_venv_deletes_marker(
        self, tmp_path, monkeypatch
    ):
        """Simulates uninstall flow: restore_venv deletes marker, then
        clear_remote_config runs. Should not crash."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # Write venv config + global.
        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(json.dumps({
            "patched": True,
            "gpu_type": "h100",
            "host": "10.0.0.1",
            "session_id": "sess_abc",
        }))
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_abc",
            gpu_type="h100",
        )

        # Simulate restore_venv deleting the marker.
        venv_cfg.unlink()

        # clear_remote_config should not crash, and should clean global.
        clear_remote_config(venv_path=str(venv))

        from specter_cli.config import remote_file
        assert not remote_file().is_file()
        assert not venv_cfg.exists()

    def test_clear_with_nonexistent_venv_path(self, tmp_path, monkeypatch):
        """clear_remote_config with a venv path that doesn't exist should
        not crash. Still clears global."""
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_abc",
            gpu_type="h100",
        )

        fake_venv = tmp_path / "nonexistent_venv"
        clear_remote_config(venv_path=str(fake_venv))

        from specter_cli.config import remote_file
        assert not remote_file().is_file()

    def test_venv_config_missing_host_falls_to_global(
        self, tmp_path, monkeypatch
    ):
        """Venv config with session_id but no host falls through to global."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(json.dumps({
            "patched": True,
            "gpu_type": "h100",
            "session_id": "sess_partial",
            # Missing host.
        }))

        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))
        config = load_remote_config()
        # Should fall through because host is missing.
        assert config["session_id"] == "sess_global"

    def test_venv_config_missing_session_id_falls_to_global(
        self, tmp_path, monkeypatch
    ):
        """Venv config with host but no session_id falls through to global."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(json.dumps({
            "patched": True,
            "gpu_type": "h100",
            "host": "10.0.0.2",
            # Missing session_id.
        }))

        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))
        config = load_remote_config()
        assert config["session_id"] == "sess_global"

    def test_global_config_is_last_write_wins(self, tmp_path, monkeypatch):
        """When two venvs save config, global holds the last one written."""
        venv_a = tmp_path / "venv_a"
        venv_b = tmp_path / "venv_b"
        venv_a.mkdir()
        venv_b.mkdir()

        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_a",
            gpu_type="h100",
            venv_path=str(venv_a),
        )
        save_remote_config(
            host="10.0.0.2",
            user="root",
            session_id="sess_b",
            gpu_type="a100",
            venv_path=str(venv_b),
        )

        # Global should have the last write (venv_b).
        from specter_cli.config import remote_file
        global_data = json.loads(remote_file().read_text())
        assert global_data["session_id"] == "sess_b"

        # But per-venv configs should be independent.
        venv_a_data = json.loads((venv_a / ".specter.json").read_text())
        assert venv_a_data["session_id"] == "sess_a"
        venv_b_data = json.loads((venv_b / ".specter.json").read_text())
        assert venv_b_data["session_id"] == "sess_b"


class TestWrapperVirtualEnvExport:
    """Verify the wrapper template includes VIRTUAL_ENV export."""

    def _make_standard_venv(self, tmp_path: Path) -> Path:
        """Create a fake venv with the expected structure."""
        venv = tmp_path / "myvenv"
        bin_dir = venv / "bin"
        bin_dir.mkdir(parents=True)

        python_bin = bin_dir / "python"
        python_bin.write_text("#!/usr/bin/env python3\n# real python\n")
        python_bin.chmod(python_bin.stat().st_mode | stat.S_IEXEC)

        python3_bin = bin_dir / "python3"
        python3_bin.symlink_to("python")

        return venv

    def test_wrapper_exports_virtual_env(self, tmp_path):
        """Wrapper should export VIRTUAL_ENV for non-activated invocations."""
        from specter_cli.venv import patch_venv

        venv = self._make_standard_venv(tmp_path)
        patch_venv(venv, "h100")

        wrapper = (venv / "bin" / "python").read_text()
        assert 'export VIRTUAL_ENV=' in wrapper
        # Must use fallback: ${VIRTUAL_ENV:-...} to not override activated venvs.
        assert 'VIRTUAL_ENV:-' in wrapper

    def test_wrapper_fallback_resolves_to_venv_root(self, tmp_path):
        """VIRTUAL_ENV fallback should resolve to the venv root directory."""
        from specter_cli.venv import patch_venv

        venv = self._make_standard_venv(tmp_path)
        patch_venv(venv, "h100")

        wrapper = (venv / "bin" / "python").read_text()
        # The fallback computes the parent of bin/: $(cd "$(dirname "$0")/.." && pwd)
        assert '"$(dirname "$0")/.."' in wrapper

    def test_wrapper_does_not_override_activated_venv(self, tmp_path):
        """When VIRTUAL_ENV is already set, wrapper should not override it."""
        from specter_cli.venv import patch_venv

        venv = self._make_standard_venv(tmp_path)
        patch_venv(venv, "h100")

        wrapper = (venv / "bin" / "python").read_text()
        # ${VIRTUAL_ENV:-fallback} means: use VIRTUAL_ENV if set, else fallback.
        assert '${VIRTUAL_ENV:-' in wrapper
