"""Tests for config freeze/readonly enforcement."""
from __future__ import annotations

import asyncio
import os
import tempfile
from types import MappingProxyType

import pytest

from shared.config import _freeze


# ---------------------------------------------------------------------------
# _freeze
# ---------------------------------------------------------------------------

class TestFreeze:
    def test_freezes_dict(self):
        result = _freeze({"a": 1, "b": 2})
        assert isinstance(result, MappingProxyType)
        assert result["a"] == 1

    def test_frozen_dict_is_immutable(self):
        result = _freeze({"a": 1})
        with pytest.raises(TypeError):
            result["a"] = 2
        with pytest.raises(TypeError):
            result["new"] = 3

    def test_freezes_list_to_tuple(self):
        result = _freeze([1, 2, 3])
        assert isinstance(result, tuple)
        assert result == (1, 2, 3)

    def test_freezes_set_to_frozenset(self):
        result = _freeze({1, 2, 3})
        assert isinstance(result, frozenset)
        assert result == frozenset({1, 2, 3})

    def test_freezes_nested_dict(self):
        result = _freeze({"a": {"b": {"c": 1}}})
        assert isinstance(result, MappingProxyType)
        assert isinstance(result["a"], MappingProxyType)
        assert isinstance(result["a"]["b"], MappingProxyType)
        assert result["a"]["b"]["c"] == 1

    def test_freezes_dict_with_list(self):
        result = _freeze({"items": [1, 2, 3]})
        assert isinstance(result["items"], tuple)

    def test_freezes_list_of_dicts(self):
        result = _freeze([{"a": 1}, {"b": 2}])
        assert isinstance(result, tuple)
        assert isinstance(result[0], MappingProxyType)

    def test_preserves_scalars(self):
        assert _freeze(42) == 42
        assert _freeze("hello") == "hello"
        assert _freeze(3.14) == 3.14
        assert _freeze(True) is True
        assert _freeze(None) is None

    def test_freezes_tuple_recursively(self):
        result = _freeze(({"a": 1}, [2, 3]))
        assert isinstance(result, tuple)
        assert isinstance(result[0], MappingProxyType)
        assert isinstance(result[1], tuple)

    def test_complex_config_structure(self):
        """Simulates a real app-config.yaml structure."""
        raw = {
            "app_urls": {
                "blog": "https://blog.rose-ash.com",
                "market": "https://market.rose-ash.com",
            },
            "features": ["sx", "federation"],
            "limits": {"max_upload": 10485760},
        }
        frozen = _freeze(raw)
        assert frozen["app_urls"]["blog"] == "https://blog.rose-ash.com"
        assert frozen["features"] == ("sx", "federation")
        with pytest.raises(TypeError):
            frozen["app_urls"]["blog"] = "changed"


# ---------------------------------------------------------------------------
# init_config / config / as_plain / pretty
# ---------------------------------------------------------------------------

class TestConfigInit:
    def test_init_and_read(self):
        """Test full init_config → config() → as_plain() → pretty() cycle."""
        import shared.config as cfg

        # Save original state
        orig_frozen = cfg._data_frozen
        orig_plain = cfg._data_plain

        try:
            # Reset state
            cfg._data_frozen = None
            cfg._data_plain = None

            # Write a temp YAML file
            with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
                f.write("app_urls:\n  blog: https://blog.example.com\nport: 8001\n")
                path = f.name

            try:
                asyncio.run(cfg.init_config(path, force=True))

                c = cfg.config()
                assert c["app_urls"]["blog"] == "https://blog.example.com"
                assert c["port"] == 8001
                assert isinstance(c, MappingProxyType)

                plain = cfg.as_plain()
                assert isinstance(plain, dict)
                assert plain["port"] == 8001
                # Modifying plain should not affect config
                plain["port"] = 9999
                assert cfg.config()["port"] == 8001

                pretty_str = cfg.pretty()
                assert "blog" in pretty_str
            finally:
                os.unlink(path)
        finally:
            # Restore original state
            cfg._data_frozen = orig_frozen
            cfg._data_plain = orig_plain

    def test_config_raises_before_init(self):
        import shared.config as cfg
        orig = cfg._data_frozen
        try:
            cfg._data_frozen = None
            with pytest.raises(RuntimeError, match="init_config"):
                cfg.config()
        finally:
            cfg._data_frozen = orig

    def test_file_not_found(self):
        import shared.config as cfg
        orig = cfg._data_frozen
        try:
            cfg._data_frozen = None
            with pytest.raises(FileNotFoundError):
                asyncio.run(cfg.init_config("/nonexistent/path.yaml", force=True))
        finally:
            cfg._data_frozen = orig
