"""Tests for launcher.config module."""

from __future__ import annotations

import json
import os
import stat
from pathlib import Path
from unittest.mock import patch


class TestGetTribunalHome:
    """Test get_tribunal_home() directory creation and env override."""

    def test_creates_directory_if_missing(self, tmp_path: Path):
        """Should create the directory when it does not exist."""
        home = tmp_path / ".tribunal"
        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_tribunal_home

            result = get_tribunal_home()
        assert result == home
        assert home.is_dir()

    def test_respects_tribunal_home_env(self, tmp_path: Path):
        """Should use SKILLFIELD_HOME env var when set."""
        custom = tmp_path / "custom"
        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(custom)}):
            from launcher.config import get_tribunal_home

            result = get_tribunal_home()
        assert result == custom

    def test_defaults_to_dot_tribunal(self):
        """Should default to ~/.tribunal when env var not set."""
        with patch.dict("os.environ", {}, clear=False):
            import os

            os.environ.pop("SKILLFIELD_HOME", None)
            from launcher.config import get_tribunal_home

            result = get_tribunal_home()
        assert result.name == ".tribunal"


class TestGetConfig:
    """Test config loading with defaults and error handling."""

    def test_loads_valid_config(self, tmp_path: Path):
        """Should load and return valid JSON config."""
        home = tmp_path / ".tribunal"
        home.mkdir()
        cfg = home / "config.json"
        cfg.write_text(
            json.dumps({"env": {"FOO": "bar"}, "default_args": ["--verbose"], "mode": "spec", "auto_update": False})
        )

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_config

            result = get_config()

        assert result["env"] == {"FOO": "bar"}
        assert result["default_args"] == ["--verbose"]
        assert result["mode"] == "spec"
        assert result["auto_update"] is False

    def test_returns_defaults_for_missing_file(self, tmp_path: Path):
        """Should return defaults and write them when config file missing."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_config

            result = get_config()

        assert result["env"] == {}
        assert result["default_args"] == []
        assert result["mode"] == "quick"
        assert result["auto_update"] is True
        assert (home / "config.json").is_file()

    def test_handles_corrupt_json(self, tmp_path: Path):
        """Should return defaults when config file has invalid JSON."""
        home = tmp_path / ".tribunal"
        home.mkdir()
        (home / "config.json").write_text("{invalid json!!!")

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_config

            result = get_config()

        assert result["mode"] == "quick"

    def test_fills_missing_keys_with_defaults(self, tmp_path: Path):
        """Should fill in missing keys from defaults."""
        home = tmp_path / ".tribunal"
        home.mkdir()
        (home / "config.json").write_text(json.dumps({"mode": "spec"}))

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_config

            result = get_config()

        assert result["mode"] == "spec"
        assert result["env"] == {}
        assert result["auto_update"] is True
        assert result["model"] is None

    def test_default_config_includes_model_key(self, tmp_path: Path):
        """Should include model key with None default in config."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_config

            result = get_config()

        assert "model" in result
        assert result["model"] is None

    def test_default_config_includes_providers_key(self, tmp_path: Path):
        """Should include providers key with empty dict default in config."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_config

            result = get_config()

        assert "providers" in result
        assert result["providers"] == {}


class TestSaveConfig:
    """Test config persistence."""

    def test_writes_json_file(self, tmp_path: Path):
        """Should write config as formatted JSON."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import save_config

            save_config({"env": {}, "default_args": [], "mode": "quick", "auto_update": True})

        data = json.loads((home / "config.json").read_text())
        assert data["mode"] == "quick"

    def test_creates_parent_dirs(self, tmp_path: Path):
        """Should create parent directories if they don't exist."""
        home = tmp_path / "deep" / "nested" / ".tribunal"

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import save_config

            save_config({"env": {}, "default_args": [], "mode": "quick", "auto_update": True})

        assert (home / "config.json").is_file()

    def test_save_config_sets_mode_600(self, tmp_path: Path):
        """config.json must be owner-readable-only (0o600)."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import save_config

            save_config({"env": {}, "default_args": [], "mode": "quick", "auto_update": True})

        cfg = home / "config.json"
        file_mode = stat.S_IMODE(os.stat(cfg).st_mode)
        assert file_mode == 0o600, f"Expected 0o600, got {oct(file_mode)}"


class TestDirectoryAccessors:
    """Test subdirectory creation helpers."""

    def test_get_sessions_dir(self, tmp_path: Path):
        """Should create and return sessions subdirectory."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_sessions_dir

            result = get_sessions_dir()

        assert result == home / "sessions"
        assert result.is_dir()

    def test_get_memory_dir(self, tmp_path: Path):
        """Should create and return memory subdirectory."""
        home = tmp_path / ".tribunal"
        home.mkdir()

        with patch.dict("os.environ", {"SKILLFIELD_HOME": str(home)}):
            from launcher.config import get_memory_dir

            result = get_memory_dir()

        assert result == home / "memory"
        assert result.is_dir()


class TestSanitizeSessionId:
    """Tests for the session ID sanitization helper."""

    def test_normal_id_unchanged(self):
        """Alphanumeric + hyphen + underscore IDs pass through untouched."""
        from launcher.config import sanitize_session_id

        assert sanitize_session_id("abc123") == "abc123"
        assert sanitize_session_id("my-session_01") == "my-session_01"

    def test_strips_path_traversal_dots(self):
        """Dots and slashes used for path traversal are removed."""
        from launcher.config import sanitize_session_id

        assert sanitize_session_id("../evil") == "evil"
        assert sanitize_session_id("../../etc/passwd") == "etcpasswd"

    def test_strips_forward_slash(self):
        """Forward slashes are removed."""
        from launcher.config import sanitize_session_id

        assert sanitize_session_id("foo/bar") == "foobar"

    def test_strips_special_chars(self):
        """Shell metacharacters are removed."""
        from launcher.config import sanitize_session_id

        assert sanitize_session_id("id;rm -rf /") == "idrm-rf"

    def test_empty_after_stripping_returns_default(self):
        """An ID that becomes empty after stripping falls back to 'default'."""
        from launcher.config import sanitize_session_id

        assert sanitize_session_id("...") == "default"
        assert sanitize_session_id("") == "default"
        assert sanitize_session_id("/") == "default"

    def test_empty_string_returns_default(self):
        """Empty string returns 'default'."""
        from launcher.config import sanitize_session_id

        assert sanitize_session_id("") == "default"
