"""Tests for specter_cli.config — local configuration management."""

from __future__ import annotations

import json

import pytest

from specter_cli.config import (
    clear_auth,
    clear_remote_config,
    deregister_session,
    get_token,
    has_active_session,
    list_sessions,
    load_auth,
    load_remote_config,
    register_session,
    save_auth,
    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 TestAuth:
    def test_no_auth_returns_empty(self):
        assert load_auth() == {}

    def test_get_token_returns_none_when_not_logged_in(self):
        assert get_token() is None

    def test_save_and_load_auth(self):
        save_auth("sk-test123")
        auth = load_auth()
        assert auth["token"] == "sk-test123"
        assert get_token() == "sk-test123"

    def test_save_auth_with_extras(self):
        save_auth("sk-test123", method="api_key", email="test@example.com")
        auth = load_auth()
        assert auth["token"] == "sk-test123"
        assert auth["method"] == "api_key"
        assert auth["email"] == "test@example.com"

    def test_clear_auth(self):
        save_auth("sk-test123")
        assert get_token() == "sk-test123"
        clear_auth()
        assert get_token() is None

    def test_clear_auth_when_no_auth(self):
        # Should not raise.
        clear_auth()

    def test_corrupted_auth_file(self):
        from specter_cli.config import auth_file

        auth_file().write_text("not json{{{")
        assert load_auth() == {}


class TestGlobalConfig:
    """Global scope session config stored in ~/.specter/remote.json."""

    def test_no_config_returns_empty(self):
        assert load_remote_config(use_global=True) == {}

    def test_save_and_load_global_config(self):
        save_remote_config(
            host="10.0.0.1",
            user="root",
            ssh_port=22,
            session_id="sess_abc",
            gpu_type="h100",
            use_global=True,
        )
        config = load_remote_config(use_global=True)
        assert config["host"] == "10.0.0.1"
        assert config["user"] == "root"
        assert config["ssh_port"] == 22
        assert config["session_id"] == "sess_abc"
        assert config["gpu_type"] == "h100"
        assert config["workspace"] == "~/.specter/workspace"

    def test_clear_global_config(self):
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_abc",
            gpu_type="h100",
            use_global=True,
        )
        assert load_remote_config(use_global=True) != {}
        clear_remote_config(use_global=True)
        assert load_remote_config(use_global=True) == {}

    def test_has_active_session_global(self):
        assert not has_active_session(use_global=True)
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_abc",
            gpu_type="h100",
            use_global=True,
        )
        assert has_active_session(use_global=True)

    def test_has_active_session_global_missing_fields(self):
        from specter_cli.config import remote_file

        # Write config missing session_id.
        remote_file().write_text(json.dumps({"host": "10.0.0.1"}))
        assert not has_active_session(use_global=True)

    def test_global_does_not_see_venv_config(self, tmp_path, monkeypatch):
        """Global scope should not read venv config."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # Write per-venv config.
        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(
            json.dumps(
                {
                    "patched": True,
                    "gpu_type": "h100",
                    "host": "10.0.0.1",
                    "session_id": "sess_venv",
                }
            )
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))

        # Global scope should return empty — no remote.json exists.
        assert load_remote_config(use_global=True) == {}
        assert not has_active_session(use_global=True)


class TestPerVenvConfig:
    """Per-venv session config stored in $VIRTUAL_ENV/.specter.json."""

    def test_venv_config_independent_of_global(self, tmp_path, monkeypatch):
        """Venv config and global config are independent scopes."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # Write per-venv config.
        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(
            json.dumps(
                {
                    "patched": True,
                    "gpu_type": "a100",
                    "host": "10.0.0.2",
                    "user": "root",
                    "session_id": "sess_venv",
                }
            )
        )

        # Write global config with different data.
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
            use_global=True,
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))

        # Venv scope returns venv config.
        venv_config = load_remote_config()
        assert venv_config["session_id"] == "sess_venv"
        assert venv_config["host"] == "10.0.0.2"

        # Global scope returns global config (not venv!).
        global_config = load_remote_config(use_global=True)
        assert global_config["session_id"] == "sess_global"
        assert global_config["host"] == "10.0.0.1"

    def test_no_fallback_to_global(self, tmp_path, monkeypatch):
        """Venv scope does NOT fall back to global config."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # Write global config only (no venv config).
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
            use_global=True,
        )

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))

        # Venv scope returns empty — no .specter.json in venv.
        assert load_remote_config() == {}
        assert not has_active_session()

    def test_venv_scope_empty_without_virtual_env(self, monkeypatch):
        """Without VIRTUAL_ENV, venv scope returns empty."""
        monkeypatch.delenv("VIRTUAL_ENV", raising=False)
        assert load_remote_config() == {}

    def test_save_writes_to_venv_only(self, tmp_path, monkeypatch):
        """save_remote_config with venv_path writes ONLY to venv."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # Write marker first (as patch_venv would).
        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(json.dumps({"patched": True, "gpu_type": "h100"}))

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

        # Venv config should have session + marker fields.
        venv_data = json.loads(venv_cfg.read_text())
        assert venv_data["session_id"] == "sess_abc"
        assert venv_data["host"] == "10.0.0.1"
        assert venv_data["patched"] is True  # Marker preserved.
        assert venv_data["venv_path"] == str(venv)

        # Global config should NOT exist (no dual-write).
        from specter_cli.config import remote_file

        assert not remote_file().is_file()

    def test_clear_removes_session_keeps_marker(self, tmp_path, monkeypatch):
        """clear_remote_config removes session fields but keeps marker."""
        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,
                    "workspace": "~/.specter/workspace",
                }
            )
        )

        clear_remote_config(venv_path=str(venv))

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

    def test_clear_venv_does_not_touch_global(self, tmp_path, monkeypatch):
        """Clearing venv scope does not affect global config."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        # Write both scopes.
        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(
            json.dumps(
                {
                    "patched": True,
                    "host": "10.0.0.2",
                    "session_id": "sess_venv",
                }
            )
        )
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
            use_global=True,
        )

        # Clear venv scope.
        clear_remote_config(venv_path=str(venv))

        # Global config should still exist.
        from specter_cli.config import remote_file

        assert remote_file().is_file()
        global_data = json.loads(remote_file().read_text())
        assert global_data["session_id"] == "sess_global"

    def test_clear_global_does_not_touch_venv(self, tmp_path, monkeypatch):
        """Clearing global scope does not affect venv config."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        venv_cfg = venv / ".specter.json"
        venv_cfg.write_text(
            json.dumps(
                {
                    "patched": True,
                    "host": "10.0.0.2",
                    "session_id": "sess_venv",
                }
            )
        )
        save_remote_config(
            host="10.0.0.1",
            user="root",
            session_id="sess_global",
            gpu_type="h100",
            use_global=True,
        )

        # Clear global scope.
        clear_remote_config(use_global=True)

        # Venv config should still exist.
        venv_data = json.loads(venv_cfg.read_text())
        assert venv_data["session_id"] == "sess_venv"

    def test_two_venvs_independent(self, tmp_path, monkeypatch):
        """Two venvs can have different GPU sessions simultaneously."""
        venv_a = tmp_path / "venv_a"
        venv_b = tmp_path / "venv_b"
        venv_a.mkdir()
        venv_b.mkdir()

        # Save different sessions to each venv.
        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),
        )

        # Activate venv A.
        monkeypatch.setenv("VIRTUAL_ENV", str(venv_a))
        config_a = load_remote_config()
        assert config_a["session_id"] == "sess_a"
        assert config_a["gpu_type"] == "h100"

        # Activate venv B.
        monkeypatch.setenv("VIRTUAL_ENV", str(venv_b))
        config_b = load_remote_config()
        assert config_b["session_id"] == "sess_b"
        assert config_b["gpu_type"] == "a100"

        # Neither write touched global.
        from specter_cli.config import remote_file

        assert not remote_file().is_file()

    def test_has_active_session_per_venv(self, tmp_path, monkeypatch):
        """has_active_session reads per-venv config when VIRTUAL_ENV set."""
        venv = tmp_path / "myvenv"
        venv.mkdir()

        monkeypatch.setenv("VIRTUAL_ENV", str(venv))
        assert not has_active_session()

        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",
                }
            )
        )
        assert has_active_session()


class TestSessionRegistry:
    """Session registry in ~/.specter/sessions.json."""

    def test_empty_registry(self):
        assert list_sessions() == []

    def test_register_session(self):
        register_session("/home/user/.venv", "sess_abc", "h100")
        sessions = list_sessions()
        assert len(sessions) == 1
        assert sessions[0]["venv_path"] == "/home/user/.venv"
        assert sessions[0]["session_id"] == "sess_abc"
        assert sessions[0]["gpu_type"] == "h100"

    def test_register_multiple_sessions(self):
        register_session("/home/user/proj-a/.venv", "sess_a", "h100")
        register_session("/home/user/proj-b/.venv", "sess_b", "a100")
        sessions = list_sessions()
        assert len(sessions) == 2
        paths = {s["venv_path"] for s in sessions}
        assert "/home/user/proj-a/.venv" in paths
        assert "/home/user/proj-b/.venv" in paths

    def test_register_same_venv_replaces(self):
        """Re-installing in the same venv replaces the registry entry."""
        register_session("/home/user/.venv", "sess_old", "h100")
        register_session("/home/user/.venv", "sess_new", "a100")
        sessions = list_sessions()
        assert len(sessions) == 1
        assert sessions[0]["session_id"] == "sess_new"
        assert sessions[0]["gpu_type"] == "a100"

    def test_deregister_session(self):
        register_session("/home/user/proj-a/.venv", "sess_a", "h100")
        register_session("/home/user/proj-b/.venv", "sess_b", "a100")
        deregister_session("/home/user/proj-a/.venv")
        sessions = list_sessions()
        assert len(sessions) == 1
        assert sessions[0]["venv_path"] == "/home/user/proj-b/.venv"

    def test_deregister_nonexistent(self):
        """Deregistering a venv that's not in the registry is a no-op."""
        register_session("/home/user/.venv", "sess_abc", "h100")
        deregister_session("/home/user/other/.venv")
        sessions = list_sessions()
        assert len(sessions) == 1

    def test_deregister_from_empty_registry(self):
        """Should not crash when registry doesn't exist."""
        deregister_session("/home/user/.venv")
        assert list_sessions() == []

    def test_corrupted_registry(self):
        """Corrupted sessions.json should return empty list."""
        from specter_cli.config import sessions_file

        sessions_file().write_text("not valid json{{{")
        assert list_sessions() == []
