"""Tests for global anima TOML configuration loading and precedence helpers."""

import asyncio
import os
import tomllib
from typing import Any, cast
from pathlib import Path

import pytest
from returns.result import Failure, Success

from anima.core.model_precedence import resolve_model_precedence
from anima.core.schemas import AnimaGlobalConfig, CompactionConfig, PersonaSpec
from anima.shell.agent import create_agent
from anima.shell.model_config import load_global_config, load_compaction_config


def test_load_global_config_missing_file_returns_defaults(tmp_path: Path) -> None:
    """Missing config file resolves to empty default config."""
    config_path = tmp_path / "missing-config.toml"

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    assert result.unwrap().defaults.model is None


def test_load_global_config_reads_toml_defaults(tmp_path: Path) -> None:
    """Valid TOML config is parsed into AnimaGlobalConfig."""
    config_path = tmp_path / "config.toml"
    config_path.write_text('[defaults]\nmodel = "openai:gpt-4o-mini"\n')

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    assert result.unwrap().defaults.model == "openai:gpt-4o-mini"


def test_load_global_config_reads_context_window_tokens(tmp_path: Path) -> None:
    """defaults.context_window_tokens is validated and loaded by schema."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        '[defaults]\nmodel = "openai:gpt-4o-mini"\ncontext_window_tokens = 120000\n'
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    assert result.unwrap().defaults.context_window_tokens == 120000


def test_load_global_config_reads_tool_output_section(tmp_path: Path) -> None:
    """tool_output config section is parsed into AnimaGlobalConfig."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """
[tool_output]
profile = "default"
per_call_cap_bytes = 120000
per_turn_cap_bytes = 240000
history_strategy = "summary_placeholder"

[tool_output.provider_profiles.default]
hard_cap_per_call_bytes = 120000
hard_cap_per_turn_bytes = 240000
prefers_pagination = true

[tool_output.tool_defaults.directory_tree]
strategy = "paginate_then_excerpt"
soft_cap_bytes = 80000
max_items = 200
max_depth = 4
preserve_structured_excerpt = true
"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.tool_output.profile == "default"
    assert config.tool_output.per_call_cap_bytes == 120000
    assert config.tool_output.per_turn_cap_bytes == 240000
    assert (
        config.tool_output.provider_profiles["default"].hard_cap_per_call_bytes
        == 120000
    )
    assert (
        config.tool_output.tool_defaults["directory_tree"].strategy
        == "paginate_then_excerpt"
    )


def test_load_global_config_reads_multiple_tool_output_profiles_and_defaults(
    tmp_path: Path,
) -> None:
    """Multiple tool-output providers and defaults are loaded from TOML maps."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """
[tool_output]
profile = "default"

[tool_output.provider_profiles.cli]
hard_cap_per_call_bytes = 10000
hard_cap_per_turn_bytes = 20000

[tool_output.provider_profiles.api]
hard_cap_per_call_bytes = 12000
hard_cap_per_turn_bytes = 24000
prefers_pagination = true

[tool_output.tool_defaults.directory_tree]
soft_cap_bytes = 2000
max_items = 8

[tool_output.tool_defaults.web_search]
strategy = "paginate_then_excerpt"
max_depth = 3
"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert set(config.tool_output.provider_profiles.keys()) == {"cli", "api"}
    assert config.tool_output.tool_defaults.keys() == {"directory_tree", "web_search"}
    assert config.tool_output.provider_profiles["api"].prefers_pagination is True


def test_load_global_config_rejects_invalid_tool_output_cap_in_config(
    tmp_path: Path,
) -> None:
    """Negative caps in tool_output section fail config parsing."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        "[tool_output]\nper_call_cap_bytes = -1\nper_turn_cap_bytes = 24000\n"
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


@pytest.mark.parametrize("cap_value", [0, -1])
def test_load_global_config_rejects_non_positive_tool_output_caps(
    tmp_path: Path,
    cap_value: int,
) -> None:
    """per_call_cap_bytes and per_turn_cap_bytes reject zero/negative values."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        f"[tool_output]\nper_call_cap_bytes = {cap_value}\nper_turn_cap_bytes = 64000\n"
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


def test_load_global_config_rejects_non_positive_provider_hard_caps(
    tmp_path: Path,
) -> None:
    """Provider hard caps reject non-positive values."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """
[tool_output]
profile = "openai"

[tool_output.provider_profiles.openai]
hard_cap_per_call_bytes = 0
hard_cap_per_turn_bytes = -1
"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


def test_load_global_config_applies_tool_output_env_overrides(tmp_path: Path) -> None:
    """Tool-output env overrides replace config values when both are set."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        '[tool_output]\nprofile = "config-profile"\nper_call_cap_bytes = 50000\nper_turn_cap_bytes = 80000\n'
    )

    result = load_global_config(
        env={
            "ANIMA_CONFIG": str(config_path),
            "ANIMA_TOOL_OUTPUT_PROFILE": "env-profile",
            "ANIMA_TOOL_OUTPUT_PER_CALL_CAP_BYTES": "120000",
            "ANIMA_TOOL_OUTPUT_PER_TURN_CAP_BYTES": "240000",
        }
    )

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.tool_output.profile == "env-profile"
    assert config.tool_output.per_call_cap_bytes == 120000
    assert config.tool_output.per_turn_cap_bytes == 240000


def test_load_global_config_applies_partial_tool_output_env_override(
    tmp_path: Path,
) -> None:
    """Env override applies only the supplied fields, preserving config fallbacks."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        '[tool_output]\nprofile = "config-profile"\nper_call_cap_bytes = 50000\nper_turn_cap_bytes = 80000\n'
    )

    result = load_global_config(
        env={
            "ANIMA_CONFIG": str(config_path),
            "ANIMA_TOOL_OUTPUT_PER_CALL_CAP_BYTES": "120000",
        }
    )

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.tool_output.profile == "config-profile"
    assert config.tool_output.per_call_cap_bytes == 120000
    assert config.tool_output.per_turn_cap_bytes == 80000


def test_load_global_config_env_override_collision_profile_takes_env(
    tmp_path: Path,
) -> None:
    """When profile values collide, env profile has precedence over config profile."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """
[tool_output]
profile = "config-profile"
per_call_cap_bytes = 50000

[tool_output.provider_profiles.config-profile]
hard_cap_per_call_bytes = 50000
hard_cap_per_turn_bytes = 80000

[tool_output.provider_profiles.env-profile]
hard_cap_per_call_bytes = 120000
hard_cap_per_turn_bytes = 200000
"""
    )

    result = load_global_config(
        env={
            "ANIMA_CONFIG": str(config_path),
            "ANIMA_TOOL_OUTPUT_PROFILE": "env-profile",
        }
    )

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.tool_output.profile == "env-profile"


def test_load_global_config_missing_selected_provider_profile_falls_back_to_global_caps(
    tmp_path: Path,
) -> None:
    """Unknown selected profile keeps parsing and falls back to top-level caps."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """
[tool_output]
profile = "missing-profile"
per_call_cap_bytes = 42000
per_turn_cap_bytes = 84000

[tool_output.provider_profiles.openai]
hard_cap_per_call_bytes = 64000
hard_cap_per_turn_bytes = 128000
"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.tool_output.profile == "missing-profile"
    assert config.tool_output.per_call_cap_bytes == 42000
    assert config.tool_output.per_turn_cap_bytes == 84000


def test_tool_output_precedence_cli_over_env_over_config_contract() -> None:
    """Tool-output precedence contract: CLI > env > config for collisions."""
    from anima.shell import model_config as model_config_module

    resolved = model_config_module.resolve_tool_output_precedence(  # type: ignore[attr-defined]
        cli={
            "profile": "cli-profile",
            "per_call_cap_bytes": 64000,
        },
        env={
            "profile": "env-profile",
            "per_call_cap_bytes": 48000,
            "per_turn_cap_bytes": 96000,
        },
        config={
            "profile": "config-profile",
            "per_call_cap_bytes": 32000,
            "per_turn_cap_bytes": 64000,
        },
    )

    assert resolved["profile"] == "cli-profile"
    assert resolved["per_call_cap_bytes"] == 64000
    assert resolved["per_turn_cap_bytes"] == 96000


def test_tool_output_budget_contract_hard_cap_dominates_and_never_expands_by_default() -> (
    None
):
    """Conservative adaptation contract: hard caps dominate; no full-context expansion."""
    from anima.shell import model_config as model_config_module

    budget = model_config_module.resolve_tool_output_budget_contract(  # type: ignore[attr-defined]
        context_window_tokens=200000,
        selected_provider_profile={
            "hard_cap_per_call_bytes": 64000,
            "hard_cap_per_turn_bytes": 128000,
            "prefers_pagination": True,
        },
        selected_model_profile={
            "per_call_cap_bytes": 180000,
            "per_turn_cap_bytes": 190000,
            "expand_to_context_window": False,
        },
    )

    assert budget["per_call_cap_bytes"] == 64000
    assert budget["per_turn_cap_bytes"] == 128000
    assert budget["expand_to_context_window"] is False


def test_tool_output_budget_profile_resolves_from_config_profile_and_model_hint() -> (
    None
):
    """Profile + model hint resolution returns clamped conservative budgets."""
    from anima.shell import model_config as model_config_module

    resolved = model_config_module.resolve_tool_output_budget_profile(  # type: ignore[attr-defined]
        cli=None,
        env=None,
        config={
            "profile": "default",
            "per_call_cap_bytes": 140000,
            "per_turn_cap_bytes": 220000,
            "provider_profiles": {
                "default": {
                    "hard_cap_per_call_bytes": 64000,
                    "hard_cap_per_turn_bytes": 128000,
                    "prefers_pagination": True,
                }
            },
            "model_profiles": {
                "gpt-4o-mini": {
                    "per_call_cap_bytes": 120000,
                    "per_turn_cap_bytes": 180000,
                }
            },
        },
        model_hint="openai:gpt-4o-mini",
        provider_hint=None,
        context_window_tokens=2_000_000,
    )

    assert resolved["provider_profile_name"] == "default"
    assert resolved["model_profile_name"] == "gpt-4o-mini"
    assert resolved["per_call_cap_bytes"] == 64000
    assert resolved["per_turn_cap_bytes"] == 128000
    assert resolved["prefers_pagination"] is True


def test_tool_output_budget_profile_missing_profiles_falls_back_conservatively() -> (
    None
):
    """Unknown profile/model hints fall back to built-in conservative defaults."""
    from anima.shell import model_config as model_config_module

    resolved = model_config_module.resolve_tool_output_budget_profile(  # type: ignore[attr-defined]
        cli=None,
        env=None,
        config={
            "profile": "missing-profile",
            "provider_profiles": {},
            "model_profiles": {},
        },
        model_hint="openai:missing-model",
        provider_hint="missing-provider",
        context_window_tokens=5_000_000,
    )

    assert resolved["provider_profile_name"] is None
    assert resolved["model_profile_name"] is None
    assert resolved["per_call_cap_bytes"] == 32000
    assert resolved["per_turn_cap_bytes"] == 64000


def test_tool_output_budget_profile_is_case_and_whitespace_tolerant() -> None:
    """Normalized profile/provider hints still resolve mapped hard caps."""
    from anima.shell import model_config as model_config_module

    resolved = model_config_module.resolve_tool_output_budget_profile(  # type: ignore[attr-defined]
        cli={"profile": " OpenAI "},
        env=None,
        config={
            "provider_profiles": {
                "openai": {
                    "hard_cap_per_call_bytes": 48000,
                    "hard_cap_per_turn_bytes": 96000,
                }
            }
        },
        model_hint="openai:gpt-4o-mini",
        provider_hint=None,
        context_window_tokens=2_000_000,
    )

    assert resolved["provider_profile_name"] == "openai"
    assert resolved["per_call_cap_bytes"] == 32000
    assert resolved["per_turn_cap_bytes"] == 64000


def test_load_global_config_allows_tool_output_absent_for_backward_compatibility(
    tmp_path: Path,
) -> None:
    """Missing tool_output section defaults cleanly for existing configs."""
    config_path = tmp_path / "config.toml"
    config_path.write_text('[defaults]\nmodel = "openai:gpt-4o-mini"\n')

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.tool_output.profile is None
    assert config.tool_output.per_call_cap_bytes is None
    assert config.tool_output.per_turn_cap_bytes is None


def test_load_global_config_reads_env_section(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """[env] values are parsed and applied to process env when unset."""
    config_path = tmp_path / "config.toml"
    env_key = "ANIMA_TEST_OPENAI_API_KEY"
    monkeypatch.delenv(env_key, raising=False)
    config_path.write_text(
        """[defaults]
model = "openai:gpt-4o-mini"

[env]
ANIMA_TEST_OPENAI_API_KEY = "sk-from-config"
"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = cast(Any, result.unwrap())
    assert config.env[env_key] == "sk-from-config"
    assert os.environ.get(env_key) == "sk-from-config"


def test_load_global_config_does_not_override_existing_env_value(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Configured [env] values do not overwrite already-set process env."""
    config_path = tmp_path / "config.toml"
    env_key = "ANIMA_TEST_OPENAI_API_KEY"
    monkeypatch.setenv(env_key, "already-set")
    config_path.write_text(
        """[env]
ANIMA_TEST_OPENAI_API_KEY = "sk-from-config"
"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    assert result.unwrap().env[env_key] == "sk-from-config"
    assert os.environ.get(env_key) == "already-set"


def test_load_global_config_caches_by_path_and_mtime(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """Repeated loads with unchanged file avoid duplicate reads."""
    config_path = tmp_path / "config.toml"
    config_path.write_text('[defaults]\nmodel = "openai:gpt-4o-mini"\n')

    read_count = 0
    original_read_bytes = Path.read_bytes

    def counting_read_bytes(self: Path) -> bytes:
        nonlocal read_count
        if self == config_path:
            read_count += 1
        return original_read_bytes(self)

    monkeypatch.setattr(Path, "read_bytes", counting_read_bytes)

    first = load_global_config(env={"ANIMA_CONFIG": str(config_path)})
    second = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(first, Success)
    assert isinstance(second, Success)
    assert read_count == 1


def test_load_global_config_invalid_toml_returns_failure(tmp_path: Path) -> None:
    """Invalid TOML returns InvalidInputError failure."""
    config_path = tmp_path / "config.toml"
    config_path.write_text("[defaults\nmodel='bad'\n")

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


def test_load_global_config_rejects_invalid_context_window_tokens(
    tmp_path: Path,
) -> None:
    """Invalid defaults.context_window_tokens values are schema rejected."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        '[defaults]\nmodel = "openai:gpt-4o-mini"\ncontext_window_tokens = 0\n'
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


def test_load_global_config_parses_tool_output_section(tmp_path: Path) -> None:
    """Tool-output section parses and populates governance fields."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """[defaults]\nmodel = "openai:gpt-4o-mini"\n\n[tool_output]\nprofile = "default"\nper_call_cap_bytes = 120000\nper_turn_cap_bytes = 240000\nhistory_strategy = "summary_placeholder"\n\n[tool_output.provider_profiles.default]\nhard_cap_per_call_bytes = 160000\nhard_cap_per_turn_bytes = 320000\nprefers_pagination = true\n\n[tool_output.tool_defaults.directory_tree]\nstrategy = "paginate_then_excerpt"\nsoft_cap_bytes = 80000\nmax_items = 200\nmax_depth = 4\npreserve_structured_excerpt = true\n"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = cast(Any, result.unwrap())

    assert config.tool_output.profile == "default"
    assert config.tool_output.per_call_cap_bytes == 120000
    assert config.tool_output.per_turn_cap_bytes == 240000
    assert config.tool_output.history_strategy == "summary_placeholder"

    default_profile = config.tool_output.provider_profiles["default"]
    assert default_profile.hard_cap_per_call_bytes == 160000
    assert default_profile.hard_cap_per_turn_bytes == 320000
    assert default_profile.prefers_pagination is True

    directory_tree = config.tool_output.tool_defaults["directory_tree"]
    assert directory_tree.strategy == "paginate_then_excerpt"
    assert directory_tree.soft_cap_bytes == 80000
    assert directory_tree.max_items == 200
    assert directory_tree.max_depth == 4
    assert directory_tree.preserve_structured_excerpt is True


def test_load_global_config_rejects_invalid_tool_output_caps(tmp_path: Path) -> None:
    """tool_output cap fields reject non-positive values."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """[defaults]\nmodel = "openai:gpt-4o-mini"\n\n[tool_output]\nper_call_cap_bytes = 0\nper_turn_cap_bytes = -1\n\n[tool_output.provider_profiles.default]\nhard_cap_per_call_bytes = 0\nhard_cap_per_turn_bytes = -1\n"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


def test_load_global_config_tool_output_env_override_partial_and_full(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """ANIMA_TOOL_OUTPUT_* env values override matching config keys."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """[defaults]\nmodel = "openai:gpt-4o-mini"\n\n[tool_output]\nprofile = "default"\nper_call_cap_bytes = 120000\nper_turn_cap_bytes = 240000\n"""
    )

    monkeypatch.delenv("ANIMA_TOOL_OUTPUT_PER_CALL_CAP_BYTES", raising=False)
    monkeypatch.delenv("ANIMA_TOOL_OUTPUT_PER_TURN_CAP_BYTES", raising=False)
    monkeypatch.delenv("ANIMA_TOOL_OUTPUT_PROFILE", raising=False)
    monkeypatch.setenv("ANIMA_TOOL_OUTPUT_PROFILE", "cli-profile")
    monkeypatch.setenv("ANIMA_TOOL_OUTPUT_PER_CALL_CAP_BYTES", "512000")

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = cast(Any, result.unwrap())
    assert config.tool_output.profile == "cli-profile"
    assert config.tool_output.per_call_cap_bytes == 512000
    assert config.tool_output.per_turn_cap_bytes == 240000


def test_load_global_config_missing_tool_output_section_uses_defaults(
    tmp_path: Path,
) -> None:
    """Missing tool_output section still loads config and exposes defaults."""
    config_path = tmp_path / "config.toml"
    config_path.write_text('[defaults]\nmodel = "openai:gpt-4o-mini"\n')

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    payload = result.unwrap().model_dump()
    assert "tool_output" in payload


def test_load_global_config_rejects_invalid_tool_defaults_caps(tmp_path: Path) -> None:
    """Tool-output per-tool defaults reject non-positive caps and counts."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        """[defaults]\nmodel = \"openai:gpt-4o-mini\"\n\n[tool_output]\n[tool_output.tool_defaults.directory_tree]\nmax_items = 0\nsoft_cap_bytes = -1\n"""
    )

    result = load_global_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())


def test_resolve_model_precedence_prefers_highest_priority() -> None:
    """Model resolution follows CLI > env > config > persona order."""
    assert (
        resolve_model_precedence(
            cli_model="cli-model",
            env_model="env-model",
            config_model="config-model",
            persona_model="persona-model",
        )
        == "cli-model"
    )

    assert (
        resolve_model_precedence(
            cli_model=None,
            env_model="env-model",
            config_model="config-model",
            persona_model="persona-model",
        )
        == "env-model"
    )

    assert (
        resolve_model_precedence(
            cli_model=None,
            env_model=None,
            config_model="config-model",
            persona_model="persona-model",
        )
        == "config-model"
    )

    assert (
        resolve_model_precedence(
            cli_model=None,
            env_model=None,
            config_model=None,
            persona_model="persona-model",
        )
        == "persona-model"
    )


def test_create_agent_precedence_env_over_persona(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """create_agent uses env model before PersonaSpec model."""
    monkeypatch.setenv("ANIMA_DEFAULT_MODEL", "openai:gpt-4o-mini")

    persona = PersonaSpec(
        id="test",
        description="A test persona.",
        prompt="You are a test agent.",
        model="openai:gpt-4",
    )

    result = asyncio.run(create_agent(persona))

    assert isinstance(result, Success)
    agent = result.unwrap()
    assert isinstance(agent.model, str)
    assert agent.model == "openai:gpt-4o-mini"


def test_create_agent_precedence_cli_over_env_and_config(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """create_agent model_override has highest precedence."""
    config_path = tmp_path / "config.toml"
    config_path.write_text('[defaults]\nmodel = "openai:gpt-4.1-mini"\n')
    monkeypatch.setenv("ANIMA_CONFIG", str(config_path))
    monkeypatch.setenv("ANIMA_DEFAULT_MODEL", "openai:gpt-4o-mini")

    persona = PersonaSpec(
        id="test",
        description="A test persona.",
        prompt="You are a test agent.",
        model="openai:gpt-4",
    )

    result = asyncio.run(create_agent(persona, model_override="openai:gpt-5"))

    assert isinstance(result, Success)
    agent = result.unwrap()
    assert isinstance(agent.model, str)
    assert agent.model == "openai:gpt-5"


def test_config_example_matches_anima_global_config_schema() -> None:
    """Example config parses and validates with AnimaGlobalConfig."""
    example_path = Path(__file__).resolve().parents[1] / "config.toml.example"
    raw = example_path.read_text(encoding="utf-8")
    parsed = tomllib.loads(raw)

    config = AnimaGlobalConfig(**parsed)

    assert config.defaults.model is None
    assert config.env == {}


# Tests for load_compaction_config


def test_load_compaction_config_missing_file_returns_defaults(tmp_path: Path) -> None:
    """Missing config file resolves to default CompactionConfig."""
    config_path = tmp_path / "missing-config.toml"

    result = load_compaction_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert isinstance(config, CompactionConfig)
    assert config.auto is True
    assert config.prune is True
    assert config.reserved == 20000
    assert config.prompt is None


def test_load_compaction_config_reads_from_global_config(tmp_path: Path) -> None:
    """CompactionConfig is extracted from loaded global config."""
    config_path = tmp_path / "config.toml"
    config_path.write_text(
        '[defaults]\nmodel = "openai:gpt-4o-mini"\n\n[compaction]\nauto = false\nreserved = 50000\n'
    )

    result = load_compaction_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.auto is False
    assert config.reserved == 50000


def test_load_compaction_config_returns_defaults_when_no_compaction_section(
    tmp_path: Path,
) -> None:
    """Config without [compaction] section returns default CompactionConfig."""
    config_path = tmp_path / "config.toml"
    config_path.write_text('[defaults]\nmodel = "openai:gpt-4o-mini"\n')

    result = load_compaction_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Success)
    config = result.unwrap()
    assert config.auto is True  # Default value
    assert config.reserved == 20000  # Default value


def test_load_compaction_config_invalid_toml_returns_failure(tmp_path: Path) -> None:
    """Invalid TOML returns InvalidInputError failure."""
    config_path = tmp_path / "config.toml"
    config_path.write_text("[defaults\nmodel='bad'\n")

    result = load_compaction_config(env={"ANIMA_CONFIG": str(config_path)})

    assert isinstance(result, Failure)
    assert "Invalid config file" in str(result.failure())
