"""Regression tests for 3 pre-existing bugs in AppController.

Bug 1: _handle_reset_session zeroes mma_tier_usage to empty dicts; the downstream
_flush_to_project crashes with KeyError: 'model'. (Commits fe240db4 introduced.)
Bug 2: __init__ does not set self.context_preset_manager; save_context_preset
and load_context_preset crash. (Lost in 72f8f466.)
Bug 3: __getattr__ returns None for 'persona_manager', making hasattr() return
True (the accompanying comment claims False, which is wrong).

The integration symptom of Bug 1 was test_context_sim_live polling ai_status
for 60s and seeing the constant 'switching to: temp_livecontextsim (stale ui -
ops disabled)' string (older runs) or 'error: \\'model\\'' (newer runs after
sim_context.py added an 'error in s' early-break check).

These tests exercise the exact code paths that were crashing, in isolation,
to prove the fixes prevent the original failures.

The tests do NOT require the live_gui fixture. They use a real AppController()
with a tmp_path for the project file, matching the pattern in
tests/test_handle_reset_session_clears_project.py.
"""
import pytest
import tomllib
from pathlib import Path

from src.app_controller import AppController


@pytest.fixture
def controller(tmp_path: Path) -> AppController:
 """Build a real AppController with a writable project file."""
 proj_path = tmp_path / "test_project.toml"
 proj_path.write_text("[project]\nname = 'TestProject'\n")
 ctrl = AppController()
 ctrl.active_project_path = str(proj_path)
 # _flush_to_project reads several UI flags that __init__ does not set
 # (ui_project_preset_name, ui_word_wrap, ui_gemini_cli_path,
 # ui_auto_add_history). Set them so the test exercises the
 # mma_tier_usage code path without tripping on unrelated missing attrs.
 ctrl.ui_project_preset_name = None
 ctrl.ui_word_wrap = True
 ctrl.ui_gemini_cli_path = ""
 ctrl.ui_auto_add_history = False
 yield ctrl


def test_reset_session_makes_flush_to_project_not_crash(controller: AppController) -> None:
 """Bug 1 fix: After _handle_reset_session, _flush_to_project must not raise KeyError.

 Pre-fix: the reset zeroes mma_tier_usage to empty dicts; _flush_to_project
 crashes on d['model']. Post-fix: the reset pre-populates the dicts (matching
 __init__ defaults), and _flush_to_project uses d.get('model') as a defensive
 fallback. This test asserts the round-trip works.
 """
 for tier in ("Tier 1", "Tier 2", "Tier 3", "Tier 4"):
  assert "model" in controller.mma_tier_usage[tier], (
   f"precondition failed: tier {tier} has no 'model' key in __init__"
  )
 controller._handle_reset_session()
 for tier in ("Tier 1", "Tier 2", "Tier 3", "Tier 4"):
  assert "model" in controller.mma_tier_usage[tier], (
   f"_handle_reset_session stripped 'model' from {tier}: "
   f"{controller.mma_tier_usage[tier]!r}"
  )
  assert "provider" in controller.mma_tier_usage[tier], (
   f"_handle_reset_session stripped 'provider' from {tier}: "
   f"{controller.mma_tier_usage[tier]!r}"
  )
 controller._flush_to_project()
 assert Path(controller.active_project_path).exists()


def test_flush_to_project_is_defensive_against_partial_tier_dict(controller: AppController) -> None:
 """Bug 1 fix (defense in depth): _flush_to_project must not raise KeyError on partial dicts.

 This is the defense-in-depth test for the d.get('model') change. Simulates
 a code path (like _handle_mma_state_update at line 484-497) that replaces
 the entire mma_tier_usage[tier] entry with a partial dict.
 """
 controller.mma_tier_usage["Tier 3"] = {"input": 0, "output": 0, "provider": "gemini"}
 controller._flush_to_project()
 with open(controller.active_project_path, "rb") as f:
  saved = tomllib.load(f)
 tier_models = saved.get("mma", {}).get("tier_models", {})
 assert "Tier 3" in tier_models, f"Tier 3 missing from saved tier_models: {tier_models!r}"
 assert tier_models["Tier 3"].get("model") in (None, ""), (
  f"Expected None or empty model for the partial-dict case, got "
  f"{tier_models['Tier 3'].get('model')!r}"
 )


def test_context_preset_manager_is_initialized(controller: AppController) -> None:
 """Bug 2 fix: self.context_preset_manager must be a ContextPresetManager, not None.

 Pre-fix: __init__ did not set self.context_preset_manager; save_context_preset
 and load_context_preset both crashed with AttributeError. Post-fix: __init__
 sets it to ContextPresetManager() (the line was lost in 72f8f466 and re-added).
 """
 assert controller.context_preset_manager is not None, (
  f"context_preset_manager is None; the __init__ line is missing"
 )
 from src.context_presets import ContextPresetManager
 assert isinstance(controller.context_preset_manager, ContextPresetManager), (
  f"context_preset_manager is {type(controller.context_preset_manager).__name__}, "
  f"expected ContextPresetManager"
 )


def test_hasattr_persona_manager_returns_false_for_fresh_controller() -> None:
 """Bug 3 fix: hasattr(ctrl, 'persona_manager') must be False for a fresh AppController.

 Pre-fix: __getattr__ returned None for 'persona_manager' (in _LAZY_MANAGER_DEFAULTS),
 making hasattr() return True. The comment claimed hasattr() returns False but
 that's wrong. Post-fix: 'persona_manager' is removed from _LAZY_MANAGER_DEFAULTS,
 so __getattr__ raises AttributeError, so hasattr() returns False.
 """
 ctrl = AppController()
 assert not hasattr(ctrl, "persona_manager"), (
  f"hasattr(ctrl, 'persona_manager') returned True for a fresh AppController. "
  f"__getattr__ likely still returns None for it. Check _LAZY_MANAGER_DEFAULTS "
  f"in src/app_controller.py."
 )
