# #region TestConfigManagerCompat [TYPE Module] [C:3] [SEMANTICS config-manager, compatibility, payload, tests]
# @BRIEF Verifies ConfigManager compatibility wrappers preserve legacy payload sections.
# @LAYER Domain
# @RELATION BINDS_TO -> ConfigManager
from types import SimpleNamespace

from src.core.config_manager import ConfigManager
from src.core.config_models import AppConfig, Environment, GlobalSettings


# #region test_get_payload_preserves_legacy_sections [TYPE Function]
# @RELATION BINDS_TO -> TestConfigManagerCompat
# @BRIEF Ensure get_payload merges typed config into raw payload without dropping legacy sections.
def test_get_payload_preserves_legacy_sections():
    manager = ConfigManager.__new__(ConfigManager)
    manager.raw_payload = {"notifications": {"smtp": {"host": "mail.local"}}}
    manager.config = AppConfig(environments=[], settings=GlobalSettings())
    payload = manager.get_payload()
    assert payload["settings"]["migration_sync_cron"] == "0 2 * * *"
    assert payload["notifications"]["smtp"]["host"] == "mail.local"
# #endregion test_get_payload_preserves_legacy_sections
# #region test_save_config_accepts_raw_payload_and_keeps_extras [TYPE Function]
# @RELATION BINDS_TO -> TestConfigManagerCompat
# @BRIEF Ensure save_config accepts raw dict payload, refreshes typed config, and preserves extra sections.
def test_save_config_accepts_raw_payload_and_keeps_extras(monkeypatch):
    manager = ConfigManager.__new__(ConfigManager)
    manager.raw_payload = {}
    manager.config = AppConfig(environments=[], settings=GlobalSettings())
    persisted = {}
    def _capture_save(config, session=None):
        persisted["payload"] = manager.get_payload()
    monkeypatch.setattr(manager, "_save_config_to_db", _capture_save)
    manager.save_config(
        {
            "environments": [],
            "settings": GlobalSettings().model_dump(),
            "notifications": {"telegram": {"bot_token": "secret"}},
        }
    )
    assert manager.raw_payload["notifications"]["telegram"]["bot_token"] == "secret"
    assert manager.config.settings.migration_sync_cron == "0 2 * * *"
    assert persisted["payload"]["notifications"]["telegram"]["bot_token"] == "secret"
# #endregion test_save_config_accepts_raw_payload_and_keeps_extras
# #region test_save_config_syncs_environment_records_for_fk_backed_flows [TYPE Function]
# @RELATION BINDS_TO -> TestConfigManagerCompat
# @BRIEF Ensure saving config mirrors typed environments into relational records required by FK-backed session persistence.
#   Deletion of stale records is tested separately in test_save_config_syncs_deletions_to_persistence.
def test_save_config_syncs_environment_records_for_fk_backed_flows():
    manager = ConfigManager.__new__(ConfigManager)
    manager.raw_payload = {}
    manager.config = AppConfig(environments=[], settings=GlobalSettings())
    added_records = []
    deleted_records = []
    existing_record = SimpleNamespace(
        id="legacy-env",
        name="Legacy",
        url="http://legacy.local",
        credentials_id="legacy-user",
    )
    # #region _FakeQuery [TYPE Class] [C:1]
    # @RELATION BINDS_TO -> [test_save_config_syncs_environment_records_for_fk_backed_flows]
    # @PURPOSE: Minimal query stub returning hardcoded existing environment record list for sync tests.
    # @INVARIANT  all() always returns [existing_record]; no parameterization or filtering.
    class _FakeQuery:
        def all(self):
            return [existing_record]
    # #endregion _FakeQuery
    # #region _FakeSession [TYPE Class] [C:1]
    # @RELATION BINDS_TO -> [test_save_config_syncs_environment_records_for_fk_backed_flows]
    # @PURPOSE: Minimal SQLAlchemy session stub that captures add/delete calls for environment sync assertions.
    # @INVARIANT  query() always returns _FakeQuery; no real DB interaction.
    class _FakeSession:
        def query(self, model):
            return _FakeQuery()
        def add(self, value):
            added_records.append(value)
        def delete(self, value):
            deleted_records.append(value)
    # #endregion _FakeSession
    session = _FakeSession()
    config = AppConfig(
        environments=[
            Environment(
                id="dev",
                name="DEV",
                url="http://superset.local",
                username="demo",
                password="secret",
            )
        ],
        settings=GlobalSettings(),
    )
    manager._sync_environment_records(session, config)
    assert len(added_records) == 1
    assert added_records[0].id == "dev"
    assert added_records[0].name == "DEV"
    assert added_records[0].url == "http://superset.local"
    assert added_records[0].credentials_id == "demo"
    # Sync does NOT delete; deletion is in _delete_stale_environment_records
    assert len(deleted_records) == 0
# #endregion test_save_config_syncs_environment_records_for_fk_backed_flows
# #region test_save_config_syncs_deletions_to_persistence [TYPE Function]
# @RELATION BINDS_TO -> TestConfigManagerCompat
# @BRIEF Ensure stale environment records are deleted during save (in _delete_stale_environment_records)
#   and NOT during _load_config → _sync_environment_records (which would violate FK constraints
#   from task_records, database_mappings, etc.).
def test_save_config_syncs_deletions_to_persistence():
    manager = ConfigManager.__new__(ConfigManager)
    manager.raw_payload = {}
    manager.config = AppConfig(environments=[], settings=GlobalSettings())
    added_records = []
    deleted_records = []
    existing_record = SimpleNamespace(
        id="legacy-env",
        name="Legacy",
        url="http://legacy.local",
        credentials_id="legacy-user",
    )
    # #region _FakeQueryDel [TYPE Class] [C:1]
    # @RELATION BINDS_TO -> [test_save_config_syncs_deletions_to_persistence]
    # @PURPOSE: Minimal query stub for deletion test.
    class _FakeQuery:
        def all(self):
            return [existing_record]
    # #endregion _FakeQueryDel
    # #region _FakeSessionDel [TYPE Class] [C:1]
    # @RELATION BINDS_TO -> [test_save_config_syncs_deletions_to_persistence]
    # @PURPOSE: Minimal session stub that captures add/delete for deletion assertions.
    class _FakeSession:
        def query(self, model):
            return _FakeQuery()
        def add(self, value):
            added_records.append(value)
        def delete(self, value):
            deleted_records.append(value)
    # #endregion _FakeSessionDel
    session = _FakeSession()
    config = AppConfig(
        environments=[
            Environment(
                id="dev",
                name="DEV",
                url="http://superset.local",
                username="demo",
                password="secret",
            )
        ],
        settings=GlobalSettings(),
    )
    # Step 1: sync creates/updates (no deletion)
    manager._sync_environment_records(session, config)
    assert len(added_records) == 1
    assert added_records[0].id == "dev"
    assert len(deleted_records) == 0
    # Step 2: stale deletion removes envs not in the configured set
    manager._delete_stale_environment_records(session, config)
    assert len(deleted_records) == 1
    assert deleted_records[0] == existing_record
    assert deleted_records[0].id == "legacy-env"
# #endregion test_save_config_syncs_deletions_to_persistence
# #region test_load_config_syncs_environment_records_from_existing_db_payload [TYPE Function]
# @RELATION BINDS_TO -> TestConfigManagerCompat
# @BRIEF Ensure loading an existing DB-backed config also mirrors environment rows required by FK-backed runtime flows.
def test_load_config_syncs_environment_records_from_existing_db_payload(monkeypatch):
    manager = ConfigManager.__new__(ConfigManager)
    manager.config_path = None
    manager.raw_payload = {}
    manager.config = AppConfig(environments=[], settings=GlobalSettings())
    sync_calls = []
    closed = {"value": False}
    committed = {"value": False}
    # #region _FakeSession [TYPE Class] [C:1]
    # @RELATION BINDS_TO -> [test_load_config_syncs_environment_records_from_existing_db_payload]
    # @PURPOSE: Minimal session stub tracking commit/close signals for config load lifecycle assertions.
    # @INVARIANT  No query or add semantics; only lifecycle signal tracking.
    class _FakeSession:
        def commit(self):
            committed["value"] = True
        def close(self):
            closed["value"] = True
    # #endregion _FakeSession
    fake_session = _FakeSession()
    fake_record = SimpleNamespace(
        id="global",
        payload={
            "environments": [
                {
                    "id": "dev",
                    "name": "DEV",
                    "url": "http://superset.local",
                    "username": "demo",
                    "password": "secret",
                }
            ],
            "settings": GlobalSettings().model_dump(),
        },
    )
    monkeypatch.setattr("src.core.config_manager.SessionLocal", lambda: fake_session)
    monkeypatch.setattr(manager, "_get_record", lambda session: fake_record)
    monkeypatch.setattr(
        manager,
        "_sync_environment_records",
        lambda session, config: sync_calls.append((session, config)),
    )
    config = manager._load_config()
    assert config.environments[0].id == "dev"
    assert len(sync_calls) == 1
    assert sync_calls[0][0] is fake_session
    assert sync_calls[0][1].environments[0].id == "dev"
    assert committed["value"] is True
    assert closed["value"] is True
# #endregion test_load_config_syncs_environment_records_from_existing_db_payload
# #endregion TestConfigManagerCompat
