# #region Test.Core.ConfigManager [C:3] [TYPE Module] [SEMANTICS test,config,manager]
# @BRIEF Unit tests for ConfigManager: init, load, save, environment CRUD, encryption, legacy migration.
# @RELATION BINDS_TO -> [ConfigManager]
# @TEST_EDGE: invalid_config_path -> ValueError on empty/non-string path
# @TEST_EDGE: missing_field -> legacy file absent falls back to default
# @TEST_EDGE: invalid_type -> save_config rejects non-AppConfig/non-dict
# @TEST_EDGE: external_fail -> DB save failure triggers rollback
from pathlib import Path
import sys
from unittest.mock import MagicMock, mock_open, patch

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

import pytest

from src.core.config_models import AppConfig, Environment, FeaturesConfig, GlobalSettings

# Hardcoded fixture — frozen Fernet key for deterministic encryption tests.
_VALID_FERNET_KEY = "MldUHg5kwSAcPnnYxmhWDS6ASb6e_bWQRV5gtwHrjQ0="

_MODULE = "src.core.config_manager"


def _make_env(env_id="env1", name="Env1", password="secret", is_default=False):
    return Environment(id=env_id, name=name, url="http://x", username="u", password=password, is_default=is_default)


def _make_config(envs=None, settings=None):
    return AppConfig(environments=envs or [], settings=settings or GlobalSettings())


def _build_manager(mock_session_local=None, config_path="config.json"):
    """Build a ConfigManager bypassing __init__ side effects. Patches NOT active after return."""
    from src.core.config_manager import ConfigManager
    mgr = object.__new__(ConfigManager)
    mgr.config_path = Path(config_path)
    mgr.raw_payload = {}
    mgr.config = _make_config()
    return mgr


# ── __init__ ────────────────────────────────────────────────────────────────

class TestInit:
    # #region test_init_invalid_path_empty [C:2] [TYPE Function]
    # @BRIEF Empty string config_path raises ValueError.
    def test_init_invalid_path_empty(self):
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.logger") as m_log:
            from src.core.config_manager import ConfigManager
            with pytest.raises(ValueError, match="non-empty string"):
                ConfigManager(config_path="")
    # #endregion test_init_invalid_path_empty

    # #region test_init_invalid_path_type [C:2] [TYPE Function]
    # @BRIEF Non-string config_path raises ValueError.
    def test_init_invalid_path_type(self):
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.logger"):
            from src.core.config_manager import ConfigManager
            with pytest.raises(ValueError, match="non-empty string"):
                ConfigManager(config_path=123)
    # #endregion test_init_invalid_path_type


# ── _get_encryption_manager ─────────────────────────────────────────────────

class TestGetEncryptionManager:
    # #region test_encryption_manager_init_failure [C:2] [TYPE Function]
    # @BRIEF EncryptionManager RuntimeError yields None with cached attribute.
    def test_encryption_manager_init_failure(self):
        mgr = _build_manager(MagicMock())
        with patch(f"{_MODULE}.EncryptionManager", side_effect=RuntimeError("no key")):
            result = mgr._get_encryption_manager()
        assert result is None
        assert mgr._encryption is None
    # #endregion test_encryption_manager_init_failure

    # #region test_encryption_manager_success [C:2] [TYPE Function]
    # @BRIEF EncryptionManager init succeeds and is cached.
    def test_encryption_manager_success(self):
        mgr = _build_manager(MagicMock())
        fake_em = MagicMock()
        with patch(f"{_MODULE}.EncryptionManager", return_value=fake_em):
            result = mgr._get_encryption_manager()
        assert result is fake_em
        # Second call returns cached
        assert mgr._get_encryption_manager() is fake_em
    # #endregion test_encryption_manager_success


# ── _encrypt_env_passwords ──────────────────────────────────────────────────

class TestEncryptEnvPasswords:
    # #region test_encrypt_no_encryption [C:2] [TYPE Function]
    # @BRIEF No encryption manager → passwords unchanged.
    def test_encrypt_no_encryption(self):
        mgr = _build_manager(MagicMock())
        mgr._encryption = None
        payload = {"environments": [{"password": "secret"}]}
        mgr._encrypt_env_passwords(payload)
        assert payload["environments"][0]["password"] == "secret"
    # #endregion test_encrypt_no_encryption

    # #region test_encrypt_non_list_environments [C:2] [TYPE Function]
    # @BRIEF Non-list environments key → no-op.
    def test_encrypt_non_list_environments(self):
        mgr = _build_manager(MagicMock())
        mgr._encryption = MagicMock()
        payload = {"environments": "not-a-list"}
        mgr._encrypt_env_passwords(payload)  # should not raise
    # #endregion test_encrypt_non_list_environments

    # #region test_encrypt_skips_masked_and_empty [C:2] [TYPE Function]
    # @BRIEF Masked (********) and empty passwords are skipped.
    def test_encrypt_skips_masked_and_empty(self):
        mgr = _build_manager(MagicMock())
        em = MagicMock()
        mgr._encryption = em
        payload = {"environments": [{"password": ""}, {"password": "********"}]}
        mgr._encrypt_env_passwords(payload)
        em.encrypt.assert_not_called()
    # #endregion test_encrypt_skips_masked_and_empty

    # #region test_encrypt_skips_already_encrypted [C:2] [TYPE Function]
    # @BRIEF Password that decrypts successfully is already encrypted → skip.
    def test_encrypt_skips_already_encrypted(self):
        mgr = _build_manager(MagicMock())
        em = MagicMock()
        em.decrypt.return_value = "plaintext"
        mgr._encryption = em
        payload = {"environments": [{"password": "already-encrypted-token"}]}
        mgr._encrypt_env_passwords(payload)
        em.encrypt.assert_not_called()
    # #endregion test_encrypt_skips_already_encrypted

    # #region test_encrypt_success [C:2] [TYPE Function]
    # @BRIEF Plain password is encrypted in-place.
    def test_encrypt_success(self):
        mgr = _build_manager(MagicMock())
        em = MagicMock()
        em.decrypt.side_effect = Exception("not encrypted")
        em.encrypt.return_value = "encrypted-token"
        mgr._encryption = em
        payload = {"environments": [{"password": "plain"}]}
        mgr._encrypt_env_passwords(payload)
        assert payload["environments"][0]["password"] == "encrypted-token"
    # #endregion test_encrypt_success

    # #region test_encrypt_failure_logs [C:2] [TYPE Function]
    # @BRIEF Encryption failure logs and leaves password unchanged.
    def test_encrypt_failure_logs(self):
        mgr = _build_manager(MagicMock())
        em = MagicMock()
        em.decrypt.side_effect = Exception("not encrypted")
        em.encrypt.side_effect = RuntimeError("boom")
        mgr._encryption = em
        payload = {"environments": [{"password": "plain"}]}
        with patch(f"{_MODULE}.logger"):
            mgr._encrypt_env_passwords(payload)
        assert payload["environments"][0]["password"] == "plain"
    # #endregion test_encrypt_failure_logs


# ── _decrypt_env_passwords ──────────────────────────────────────────────────

class TestDecryptEnvPasswords:
    # #region test_decrypt_no_encryption [C:2] [TYPE Function]
    # @BRIEF No encryption manager → passwords unchanged.
    def test_decrypt_no_encryption(self):
        mgr = _build_manager(MagicMock())
        mgr._encryption = None
        config = _make_config([_make_env(password="secret")])
        mgr._decrypt_env_passwords(config)
        assert config.environments[0].password == "secret"
    # #endregion test_decrypt_no_encryption

    # #region test_decrypt_empty_password [C:2] [TYPE Function]
    # @BRIEF Empty password is skipped.
    def test_decrypt_empty_password(self):
        mgr = _build_manager(MagicMock())
        mgr._encryption = MagicMock()
        config = _make_config([_make_env(password="")])
        mgr._decrypt_env_passwords(config)
        mgr._encryption.decrypt.assert_not_called()
    # #endregion test_decrypt_empty_password

    # #region test_decrypt_success [C:2] [TYPE Function]
    # @BRIEF Encrypted password is decrypted in-place.
    def test_decrypt_success(self):
        mgr = _build_manager(MagicMock())
        em = MagicMock()
        em.decrypt.return_value = "decrypted"
        mgr._encryption = em
        config = _make_config([_make_env(password="encrypted-token")])
        mgr._decrypt_env_passwords(config)
        assert config.environments[0].password == "decrypted"
    # #endregion test_decrypt_success

    # #region test_decrypt_failure_keeps_plaintext [C:2] [TYPE Function]
    # @BRIEF Decrypt failure assumes plaintext legacy data → password unchanged.
    def test_decrypt_failure_keeps_plaintext(self):
        mgr = _build_manager(MagicMock())
        em = MagicMock()
        em.decrypt.side_effect = Exception("bad data")
        mgr._encryption = em
        config = _make_config([_make_env(password="legacy-plain")])
        mgr._decrypt_env_passwords(config)
        assert config.environments[0].password == "legacy-plain"
    # #endregion test_decrypt_failure_keeps_plaintext


# ── _apply_features_from_env ────────────────────────────────────────────────

class TestApplyFeaturesFromEnv:
    # #region test_apply_timezone_from_env [C:2] [TYPE Function]
    # @BRIEF APP_TIMEZONE env var overrides settings.app_timezone.
    def test_apply_timezone_from_env(self):
        from src.core.config_manager import ConfigManager
        settings = GlobalSettings()
        with patch.dict("os.environ", {"APP_TIMEZONE": "US/Eastern"}), \
             patch(f"{_MODULE}.belief_scope"):
            ConfigManager._apply_features_from_env(settings)
        assert settings.app_timezone == "US/Eastern"
    # #endregion test_apply_timezone_from_env

    # #region test_apply_feature_flags_from_env [C:2] [TYPE Function]
    # @BRIEF FEATURES__* env vars override feature flags.
    def test_apply_feature_flags_from_env(self):
        from src.core.config_manager import ConfigManager
        settings = GlobalSettings()
        with patch.dict("os.environ", {"FEATURES__TRANSLATE": "false"}), \
             patch(f"{_MODULE}.belief_scope"):
            ConfigManager._apply_features_from_env(settings)
        assert settings.features.translate is False
    # #endregion test_apply_feature_flags_from_env


# ── _load_from_legacy_file ──────────────────────────────────────────────────

class TestLoadFromLegacyFile:
    # #region test_legacy_file_not_found [C:2] [TYPE Function]
    # @BRIEF Missing legacy file returns empty dict.
    def test_legacy_file_not_found(self):
        mgr = _build_manager(MagicMock(), config_path="/nonexistent/config.json")
        with patch(f"{_MODULE}.belief_scope"):
            result = mgr._load_from_legacy_file()
        assert result == {}
    # #endregion test_legacy_file_not_found

    # #region test_legacy_file_valid_json [C:2] [TYPE Function]
    # @BRIEF Valid JSON object loaded from legacy file.
    def test_legacy_file_valid_json(self, tmp_path):
        cfg = tmp_path / "config.json"
        cfg.write_text('{"environments": [], "settings": {}}')
        mgr = _build_manager(MagicMock(), config_path=str(cfg))
        with patch(f"{_MODULE}.belief_scope"):
            result = mgr._load_from_legacy_file()
        assert result == {"environments": [], "settings": {}}
    # #endregion test_legacy_file_valid_json

    # #region test_legacy_file_non_dict [C:2] [TYPE Function]
    # @BRIEF Non-dict JSON payload raises ValueError.
    def test_legacy_file_non_dict(self, tmp_path):
        cfg = tmp_path / "config.json"
        cfg.write_text('"just a string"')
        mgr = _build_manager(MagicMock(), config_path=str(cfg))
        with patch(f"{_MODULE}.belief_scope"):
            with pytest.raises(ValueError, match="JSON object"):
                mgr._load_from_legacy_file()
    # #endregion test_legacy_file_non_dict


# ── _load_config ────────────────────────────────────────────────────────────

class TestLoadConfig:
    # #region test_load_from_db [C:2] [TYPE Function]
    # @BRIEF DB record present → config loaded from DB payload.
    def test_load_from_db(self):
        mock_session = MagicMock()
        record = MagicMock()
        record.id = "global"
        record.payload = {"environments": [], "settings": {}}
        mock_session.query.return_value.filter.return_value.first.return_value = record
        mock_sl = MagicMock(return_value=mock_session)

        mgr = _build_manager()
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.SessionLocal", mock_sl), \
             patch.object(mgr, "_decrypt_env_passwords"), \
             patch.object(mgr, "_sync_environment_records"):
            config = mgr._load_config()
        assert isinstance(config, AppConfig)
        mock_session.commit.assert_called_once()
    # #endregion test_load_from_db

    # #region test_load_from_legacy_migration [C:2] [TYPE Function]
    # @BRIEF No DB record + legacy file present → migration path.
    def test_load_from_legacy_migration(self, tmp_path):
        cfg = tmp_path / "config.json"
        cfg.write_text('{"environments": [], "settings": {}}')
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = None
        mock_sl = MagicMock(return_value=mock_session)

        mgr = _build_manager(config_path=str(cfg))
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.SessionLocal", mock_sl), \
             patch.object(mgr, "_decrypt_env_passwords"), \
             patch.object(mgr, "_apply_features_from_env"), patch.object(mgr, "_save_config_to_db"):
            config = mgr._load_config()
        assert isinstance(config, AppConfig)
    # #endregion test_load_from_legacy_migration

    # #region test_load_default_fallback [C:2] [TYPE Function]
    # @BRIEF No DB record + no legacy file → default config.
    def test_load_default_fallback(self):
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = None
        mock_sl = MagicMock(return_value=mock_session)

        mgr = _build_manager(config_path="/nonexistent/config.json")
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.SessionLocal", mock_sl), \
             patch.object(mgr, "_save_config_to_db"):
            config = mgr._load_config()
        assert isinstance(config, AppConfig)
        assert config.environments == []
    # #endregion test_load_default_fallback

    # #region test_load_recoverable_error [C:2] [TYPE Function]
    # @BRIEF JSON/Type/Value error during load → default config fallback.
    def test_load_recoverable_error(self):
        mock_session = MagicMock()
        mock_session.query.side_effect = ValueError("bad data")
        mock_sl = MagicMock(return_value=mock_session)

        mgr = _build_manager()
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.SessionLocal", mock_sl):
            config = mgr._load_config()
        assert isinstance(config, AppConfig)
    # #endregion test_load_recoverable_error

    # #region test_load_critical_error_reraises [C:2] [TYPE Function]
    # @BRIEF Non-recoverable exception during load is re-raised.
    def test_load_critical_error_reraises(self):
        mock_session = MagicMock()
        mock_session.query.side_effect = RuntimeError("critical")
        mock_sl = MagicMock(return_value=mock_session)

        mgr = _build_manager()
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.SessionLocal", mock_sl):
            with pytest.raises(RuntimeError, match="critical"):
                mgr._load_config()
    # #endregion test_load_critical_error_reraises


# ── _sync_environment_records ───────────────────────────────────────────────

class TestSyncEnvironmentRecords:
    # #region test_sync_creates_new_records [C:2] [TYPE Function]
    # @BRIEF New environments are added as DB records.
    def test_sync_creates_new_records(self):
        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = []
        mgr = _build_manager(MagicMock())
        config = _make_config([_make_env("env1", "Env One")])
        with patch(f"{_MODULE}.belief_scope"):
            mgr._sync_environment_records(mock_session, config)
        mock_session.add.assert_called_once()
    # #endregion test_sync_creates_new_records

    # #region test_sync_updates_existing_records [C:2] [TYPE Function]
    # @BRIEF Existing environment records are updated in-place.
    def test_sync_updates_existing_records(self):
        existing = MagicMock()
        existing.id = "env1"
        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = [existing]
        mgr = _build_manager(MagicMock())
        config = _make_config([_make_env("env1", "Updated Name")])
        with patch(f"{_MODULE}.belief_scope"):
            mgr._sync_environment_records(mock_session, config)
        assert existing.name == "Updated Name"
        mock_session.add.assert_not_called()
    # #endregion test_sync_updates_existing_records

    # #region test_sync_skips_empty_id [C:2] [TYPE Function]
    # @BRIEF Environments with empty id are skipped.
    def test_sync_skips_empty_id(self):
        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = []
        mgr = _build_manager(MagicMock())
        config = _make_config([_make_env("", "NoID")])
        with patch(f"{_MODULE}.belief_scope"):
            mgr._sync_environment_records(mock_session, config)
        mock_session.add.assert_not_called()
    # #endregion test_sync_skips_empty_id


# ── _delete_stale_environment_records ───────────────────────────────────────

class TestDeleteStaleEnvironmentRecords:
    # #region test_delete_stale_records [C:2] [TYPE Function]
    # @BRIEF Persisted records not in config are deleted.
    def test_delete_stale_records(self):
        stale = MagicMock()
        stale.id = "stale-env"
        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = [stale]
        mgr = _build_manager(MagicMock())
        config = _make_config([_make_env("env1")])
        with patch(f"{_MODULE}.belief_scope"):
            mgr._delete_stale_environment_records(mock_session, config)
        mock_session.delete.assert_called_once_with(stale)
    # #endregion test_delete_stale_records

    # #region test_delete_no_stale [C:2] [TYPE Function]
    # @BRIEF All persisted records match config → nothing deleted.
    def test_delete_no_stale(self):
        rec = MagicMock()
        rec.id = "env1"
        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = [rec]
        mgr = _build_manager(MagicMock())
        config = _make_config([_make_env("env1")])
        with patch(f"{_MODULE}.belief_scope"):
            mgr._delete_stale_environment_records(mock_session, config)
        mock_session.delete.assert_not_called()
    # #endregion test_delete_no_stale


# ── _save_config_to_db ──────────────────────────────────────────────────────

class TestSaveConfigToDb:
    # #region test_save_creates_new_record [C:2] [TYPE Function]
    # @BRIEF No existing record → new AppConfigRecord created.
    def test_save_creates_new_record(self):
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = None
        mgr = _build_manager(MagicMock())
        config = _make_config()
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "_encrypt_env_passwords"), \
             patch.object(mgr, "_decrypt_env_passwords"), patch.object(mgr, "_sync_environment_records"), \
             patch.object(mgr, "_delete_stale_environment_records"):
            mgr._save_config_to_db(config, session=mock_session)
        mock_session.add.assert_called_once()
        mock_session.commit.assert_called_once()
    # #endregion test_save_creates_new_record

    # #region test_save_updates_existing_record [C:2] [TYPE Function]
    # @BRIEF Existing record → payload updated in-place.
    def test_save_updates_existing_record(self):
        record = MagicMock()
        record.id = "global"
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = record
        mgr = _build_manager(MagicMock())
        config = _make_config()
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "_encrypt_env_passwords"), \
             patch.object(mgr, "_decrypt_env_passwords"), patch.object(mgr, "_sync_environment_records"), \
             patch.object(mgr, "_delete_stale_environment_records"):
            mgr._save_config_to_db(config, session=mock_session)
        assert record.payload is not None
        mock_session.add.assert_not_called()
    # #endregion test_save_updates_existing_record

    # #region test_save_rollback_on_error [C:2] [TYPE Function]
    # @BRIEF DB commit failure → rollback and re-raise.
    def test_save_rollback_on_error(self):
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = None
        mock_session.commit.side_effect = RuntimeError("db error")
        mgr = _build_manager(MagicMock())
        config = _make_config()
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "_encrypt_env_passwords"), \
             patch.object(mgr, "_sync_environment_records"), \
             patch.object(mgr, "_delete_stale_environment_records"):
            with pytest.raises(RuntimeError, match="db error"):
                mgr._save_config_to_db(config, session=mock_session)
        mock_session.rollback.assert_called_once()
    # #endregion test_save_rollback_on_error

    # #region test_save_owns_session_closes [C:2] [TYPE Function]
    # @BRIEF When no session provided, manager creates and closes its own.
    def test_save_owns_session_closes(self):
        mock_session = MagicMock()
        mock_session.query.return_value.filter.return_value.first.return_value = None
        mock_sl = MagicMock(return_value=mock_session)
        mgr = _build_manager()
        config = _make_config()
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.SessionLocal", mock_sl), \
             patch.object(mgr, "_encrypt_env_passwords"), \
             patch.object(mgr, "_decrypt_env_passwords"), patch.object(mgr, "_sync_environment_records"), \
             patch.object(mgr, "_delete_stale_environment_records"):
            mgr._save_config_to_db(config, session=None)
        mock_session.close.assert_called_once()
    # #endregion test_save_owns_session_closes


# ── save / get_config / get_payload ─────────────────────────────────────────

class TestPublicAccessors:
    # #region test_save_delegates [C:2] [TYPE Function]
    # @BRIEF save() delegates to _save_config_to_db with current config.
    def test_save_delegates(self):
        mgr = _build_manager(MagicMock())
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "_save_config_to_db") as m:
            mgr.save()
        m.assert_called_once_with(mgr.config)
    # #endregion test_save_delegates

    # #region test_get_config [C:2] [TYPE Function]
    # @BRIEF get_config returns current in-memory config.
    def test_get_config(self):
        mgr = _build_manager(MagicMock())
        assert mgr.get_config() is mgr.config
    # #endregion test_get_config

    # #region test_get_payload [C:2] [TYPE Function]
    # @BRIEF get_payload syncs and returns raw payload.
    def test_get_payload(self):
        mgr = _build_manager(MagicMock())
        with patch(f"{_MODULE}.belief_scope"):
            result = mgr.get_payload()
        assert isinstance(result, dict)
        assert "environments" in result
    # #endregion test_get_payload


# ── save_config ─────────────────────────────────────────────────────────────

class TestSaveConfig:
    # #region test_save_config_typed [C:2] [TYPE Function]
    # @BRIEF AppConfig input → saved and returned.
    def test_save_config_typed(self):
        mgr = _build_manager(MagicMock())
        config = _make_config()
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "_save_config_to_db"):
            result = mgr.save_config(config)
        assert result is config
    # #endregion test_save_config_typed

    # #region test_save_config_dict [C:2] [TYPE Function]
    # @BRIEF Dict input → validated and saved.
    def test_save_config_dict(self):
        mgr = _build_manager(MagicMock())
        payload = {"environments": [], "settings": {}}
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "_save_config_to_db"):
            result = mgr.save_config(payload)
        assert isinstance(result, AppConfig)
    # #endregion test_save_config_dict

    # #region test_save_config_invalid_type [C:2] [TYPE Function]
    # @BRIEF Non-AppConfig/non-dict → TypeError.
    def test_save_config_invalid_type(self):
        mgr = _build_manager(MagicMock())
        with patch(f"{_MODULE}.belief_scope"):
            with pytest.raises(TypeError, match="AppConfig or dict"):
                mgr.save_config("invalid")
    # #endregion test_save_config_invalid_type


# ── update_global_settings ──────────────────────────────────────────────────

class TestUpdateGlobalSettings:
    # #region test_update_settings [C:2] [TYPE Function]
    # @BRIEF Settings replaced and saved.
    def test_update_settings(self):
        mgr = _build_manager(MagicMock())
        new_settings = GlobalSettings(app_timezone="US/Pacific")
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            result = mgr.update_global_settings(new_settings)
        assert result.settings.app_timezone == "US/Pacific"
    # #endregion test_update_settings


# ── validate_path ───────────────────────────────────────────────────────────

class TestValidatePath:
    # #region test_validate_path_success [C:2] [TYPE Function]
    # @BRIEF Writable directory → (True, "OK").
    def test_validate_path_success(self, tmp_path):
        mgr = _build_manager(MagicMock())
        with patch(f"{_MODULE}.belief_scope"):
            ok, msg = mgr.validate_path(str(tmp_path))
        assert ok is True
        assert msg == "OK"
    # #endregion test_validate_path_success

    # #region test_validate_path_not_exist [C:2] [TYPE Function]
    # @BRIEF Path does not exist after mkdir attempt → (False, ...).
    def test_validate_path_not_exist(self):
        mgr = _build_manager(MagicMock())
        fake_path = MagicMock()
        fake_path.expanduser.return_value = fake_path
        fake_path.exists.return_value = False
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.Path", return_value=fake_path):
            ok, msg = mgr.validate_path("/nonexistent/path")
        assert ok is False
        assert "does not exist" in msg
    # #endregion test_validate_path_not_exist

    # #region test_validate_path_not_dir [C:2] [TYPE Function]
    # @BRIEF Path is a file, not a directory → (False, ...).
    def test_validate_path_not_dir(self):
        mgr = _build_manager(MagicMock())
        fake_path = MagicMock()
        fake_path.expanduser.return_value = fake_path
        fake_path.exists.return_value = True
        fake_path.is_dir.return_value = False
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.Path", return_value=fake_path):
            ok, msg = mgr.validate_path("/some/file")
        assert ok is False
        assert "not a directory" in msg
    # #endregion test_validate_path_not_dir

    # #region test_validate_path_exception [C:2] [TYPE Function]
    # @BRIEF Exception during validation → (False, error message).
    def test_validate_path_exception(self):
        mgr = _build_manager(MagicMock())
        fake_path = MagicMock()
        fake_path.expanduser.side_effect = PermissionError("denied")
        with patch(f"{_MODULE}.belief_scope"), patch(f"{_MODULE}.Path", return_value=fake_path):
            ok, msg = mgr.validate_path("/restricted/path")
        assert ok is False
        assert "denied" in msg
    # #endregion test_validate_path_exception


# ── get_environments / has_environments / get_environment ────────────────────

class TestEnvironmentAccessors:
    # #region test_get_environments [C:2] [TYPE Function]
    # @BRIEF Returns list copy of environments.
    def test_get_environments(self):
        env = _make_env()
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([env])
        with patch(f"{_MODULE}.belief_scope"):
            result = mgr.get_environments()
        assert len(result) == 1
        assert result[0].id == "env1"
    # #endregion test_get_environments

    # #region test_has_environments_true [C:2] [TYPE Function]
    # @BRIEF Returns True when environments exist.
    def test_has_environments_true(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env()])
        with patch(f"{_MODULE}.belief_scope"):
            assert mgr.has_environments() is True
    # #endregion test_has_environments_true

    # #region test_has_environments_false [C:2] [TYPE Function]
    # @BRIEF Returns False when no environments.
    def test_has_environments_false(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([])
        with patch(f"{_MODULE}.belief_scope"):
            assert mgr.has_environments() is False
    # #endregion test_has_environments_false

    # #region test_get_environment_by_id [C:2] [TYPE Function]
    # @BRIEF Lookup by id returns matching environment.
    def test_get_environment_by_id(self):
        env = _make_env("env1", "Env One")
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([env])
        with patch(f"{_MODULE}.belief_scope"):
            assert mgr.get_environment("env1") is env
    # #endregion test_get_environment_by_id

    # #region test_get_environment_by_name [C:2] [TYPE Function]
    # @BRIEF Lookup by name returns matching environment.
    def test_get_environment_by_name(self):
        env = _make_env("env1", "Env One")
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([env])
        with patch(f"{_MODULE}.belief_scope"):
            assert mgr.get_environment("Env One") is env
    # #endregion test_get_environment_by_name

    # #region test_get_environment_not_found [C:2] [TYPE Function]
    # @BRIEF Unknown id → None.
    def test_get_environment_not_found(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env()])
        with patch(f"{_MODULE}.belief_scope"):
            assert mgr.get_environment("nonexistent") is None
    # #endregion test_get_environment_not_found

    # #region test_get_environment_empty_id [C:2] [TYPE Function]
    # @BRIEF Empty/None id → None.
    def test_get_environment_empty_id(self):
        mgr = _build_manager(MagicMock())
        with patch(f"{_MODULE}.belief_scope"):
            assert mgr.get_environment("") is None
            assert mgr.get_environment(None) is None
    # #endregion test_get_environment_empty_id


# ── add_environment ─────────────────────────────────────────────────────────

class TestAddEnvironment:
    # #region test_add_new_environment [C:2] [TYPE Function]
    # @BRIEF New environment appended and saved.
    def test_add_new_environment(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([])
        env = _make_env("new1", "New Env")
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            result = mgr.add_environment(env)
        assert len(result.environments) == 1
        assert result.environments[0].id == "new1"
    # #endregion test_add_new_environment

    # #region test_add_replaces_existing [C:2] [TYPE Function]
    # @BRIEF Existing id → replaced in-place.
    def test_add_replaces_existing(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1", "Old")])
        env = _make_env("env1", "New")
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            result = mgr.add_environment(env)
        assert len(result.environments) == 1
        assert result.environments[0].name == "New"
    # #endregion test_add_replaces_existing

    # #region test_add_default_clears_others [C:2] [TYPE Function]
    # @BRIEF Adding a default env clears is_default on others.
    def test_add_default_clears_others(self):
        mgr = _build_manager(MagicMock())
        old = _make_env("env1", is_default=True)
        mgr.config = _make_config([old])
        new = _make_env("env2", is_default=True)
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.add_environment(new)
        assert mgr.config.environments[0].is_default is False
        assert mgr.config.environments[1].is_default is True
    # #endregion test_add_default_clears_others

    # #region test_add_first_env_becomes_default [C:2] [TYPE Function]
    # @BRIEF First environment auto-becomes default if none marked.
    def test_add_first_env_becomes_default(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([])
        env = _make_env("env1", is_default=False)
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.add_environment(env)
        assert mgr.config.environments[0].is_default is True
    # #endregion test_add_first_env_becomes_default


# ── update_environment ──────────────────────────────────────────────────────

class TestUpdateEnvironment:
    # #region test_update_existing [C:2] [TYPE Function]
    # @BRIEF Existing env updated and saved.
    def test_update_existing(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1", "Old", password="real")])
        updated = _make_env("env1", "New", password="new-pwd")
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            result = mgr.update_environment("env1", updated)
        assert result is True
        assert mgr.config.environments[0].name == "New"
    # #endregion test_update_existing

    # #region test_update_masked_password_preserved [C:2] [TYPE Function]
    # @BRIEF Masked password (********) preserves existing password.
    def test_update_masked_password_preserved(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1", password="real-secret")])
        updated = _make_env("env1", password="********")
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.update_environment("env1", updated)
        assert mgr.config.environments[0].password == "real-secret"
    # #endregion test_update_masked_password_preserved

    # #region test_update_not_found [C:2] [TYPE Function]
    # @BRIEF Unknown env_id → returns False.
    def test_update_not_found(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1")])
        with patch(f"{_MODULE}.belief_scope"):
            result = mgr.update_environment("nonexistent", _make_env("env2"))
        assert result is False
    # #endregion test_update_not_found

    # #region test_update_default_preservation [C:2] [TYPE Function]
    # @BRIEF Removing is_default from the only default keeps it default.
    def test_update_default_preservation(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1", is_default=True)])
        updated = _make_env("env1", is_default=False)
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.update_environment("env1", updated)
        assert mgr.config.environments[0].is_default is True
    # #endregion test_update_default_preservation

    # #region test_update_sets_default_clears_others [C:2] [TYPE Function]
    # @BRIEF Setting is_default=True on update clears is_default on all others.
    def test_update_sets_default_clears_others(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([
            _make_env("env1", is_default=True),
            _make_env("env2", is_default=False),
        ])
        updated = _make_env("env2", is_default=True)
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.update_environment("env2", updated)
        assert mgr.config.environments[0].is_default is False
        assert mgr.config.environments[1].is_default is True
    # #endregion test_update_sets_default_clears_others


# ── delete_environment ──────────────────────────────────────────────────────

class TestDeleteEnvironment:
    # #region test_delete_existing [C:2] [TYPE Function]
    # @BRIEF Existing env deleted and saved.
    def test_delete_existing(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1"), _make_env("env2")])
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            result = mgr.delete_environment("env1")
        assert result is True
        assert len(mgr.config.environments) == 1
        assert mgr.config.environments[0].id == "env2"
    # #endregion test_delete_existing

    # #region test_delete_not_found [C:2] [TYPE Function]
    # @BRIEF Unknown env_id → returns False.
    def test_delete_not_found(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1")])
        with patch(f"{_MODULE}.belief_scope"):
            result = mgr.delete_environment("nonexistent")
        assert result is False
    # #endregion test_delete_not_found

    # #region test_delete_default_reassigns [C:2] [TYPE Function]
    # @BRIEF Deleting default env → first remaining becomes default.
    def test_delete_default_reassigns(self):
        mgr = _build_manager(MagicMock())
        mgr.config = _make_config([_make_env("env1", is_default=True), _make_env("env2")])
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.delete_environment("env1")
        assert mgr.config.environments[0].is_default is True
    # #endregion test_delete_default_reassigns

    # #region test_delete_updates_default_setting [C:2] [TYPE Function]
    # @BRIEF Deleting env referenced in settings.default_environment_id updates it.
    def test_delete_updates_default_setting(self):
        mgr = _build_manager(MagicMock())
        settings = GlobalSettings(default_environment_id="env1")
        mgr.config = _make_config([_make_env("env1", is_default=True), _make_env("env2")], settings=settings)
        with patch(f"{_MODULE}.belief_scope"), patch.object(mgr, "save"):
            mgr.delete_environment("env1")
        assert mgr.config.settings.default_environment_id != "env1"
    # #endregion test_delete_updates_default_setting
#endregion Test.Core.ConfigManager


# #region TestGlobalSettingsValidators [C:2] [TYPE Module]
# @BRIEF Direct tests for GlobalSettings field validators.
import pytest


class TestAppTimezoneValidator:
    # #region test_valid_timezone [C:1] [TYPE Function]
    def test_valid_timezone(self):
        gs = GlobalSettings(app_timezone="Europe/Moscow")
        assert gs.app_timezone == "Europe/Moscow"
    # #endregion test_valid_timezone

    # #region test_invalid_timezone [C:1] [TYPE Function]
    def test_invalid_timezone(self):
        with pytest.raises(ValueError, match="Invalid IANA timezone"):
            GlobalSettings(app_timezone="Not/A/Timezone")
    # #endregion test_invalid_timezone


class TestAllowedLanguagesValidator:
    # #region test_valid_languages [C:1] [TYPE Function]
    def test_valid_languages(self):
        gs = GlobalSettings(allowed_languages=["en", "fr", "de", "zh-CN", "pt-BR"])
        assert "en" in gs.allowed_languages
        assert "zh-CN" in gs.allowed_languages
    # #endregion test_valid_languages

    # #region test_empty_language_rejected [C:1] [TYPE Function]
    def test_empty_language_rejected(self):
        with pytest.raises(ValueError, match="Empty language code"):
            GlobalSettings(allowed_languages=["en", "", "fr"])
    # #endregion test_empty_language_rejected

    # #region test_invalid_bcp47_rejected [C:1] [TYPE Function]
    def test_invalid_bcp47_rejected(self):
        with pytest.raises(ValueError, match="Invalid BCP-47"):
            GlobalSettings(allowed_languages=["en", "123"])
    # #endregion test_invalid_bcp47_rejected
# #endregion TestGlobalSettingsValidators
