from app.services.context_builder import build_system_prompt, relevant_world_bible, select_memories_for_context

conversation = {
    'persona_name': 'Mara',
    'scenario_name': 'Basgiath Test',
    'canon_mode': 'Selective Canon',
    'timeline_mode': 'Fixed',
    'reply_length': 'Long',
    'initiative': 'Balanced',
    'relationship_realism': 'Balanced',
}

noise = [
    {'stable_key': f'noise:{i}', 'details': f'Unrelated background fact {i}', 'source': 'auto-rp'}
    for i in range(90)
]
main_character = {
    'stable_key': 'scenario:character:xaden',
    'name': 'Xaden Riorson',
    'role': 'main character',
    'source': 'scenario-template-linked-character',
    'definition': {
        'personality_profile': {'traits': ['controlled', 'dryly funny', 'protective']},
        'rp_behavior': {'speech_style': 'terse, dry, specific'},
        'appearance': {'hair_color': 'black'},
    },
}
secret = {
    'stable_key': 'secret:archive',
    'details': 'The archive key is hidden beneath the old map.',
    'known_by': ['Mara'],
    'hidden_from': ['Xaden Riorson'],
    'source': 'scenario-template',
}
state = {
    'hard_canon': {
        'user_persona': {
            'name': 'Mara', 'pronouns': 'she/her', 'occupation': 'scribe',
            'appearance': 'dark curls', 'backstory': 'transferred from the Scribe Quadrant'
        },
        'scenario_rules': ['Do not decide Mara.'],
    },
    'relationship': {'phase': 'rivals'},
    'knowledge': {'facts': [{'fact': secret['details'], 'known_by': ['Mara'], 'hidden_from': ['Xaden Riorson']}]},
    'scene': {'location': 'Basgiath archive', 'present': ['Mara', 'Xaden Riorson']},
    'physical': {'continuity_notes': ['Mara is holding a silver key.']},
    'world_bible': {
        'schema_version': 4,
        'overview': {'name': 'Basgiath Test', 'description': 'A continuity regression world.'},
        'background_facts': noise,
        'characters': [main_character],
        'secrets_knowledge': [secret],
        'core_canon': [{'details': 'Mara controls her own choices.', 'source': 'user-created'}],
        'world_rules': [{'details': 'Dragon bonds are permanent.', 'source': 'scenario-template'}],
        'current_state': [{'details': 'The archive is under lockdown.', 'source': 'auto-rp'}],
        'physical_continuity': [{'details': 'The west door is barred.', 'source': 'auto-rp'}],
    },
}
memories = [
    {
        'id': 1,
        'active': 1,
        'title': 'Archive confession',
        'what_happened': 'Mara admitted she had copied the restricted map.',
        'content': 'Mara admitted she had copied the restricted map.',
        'who_remembers': ['Mara'],
        'who_involved': ['Mara', 'Xaden Riorson'],
        'story_when': 'last night',
        'story_where': 'archive',
        'memory_type': 'Secret Learned',
        'importance': 'Critical',
        'current_relevance': 'active',
        'source_kind': 'Auto-created from RP',
    },
    {
        'id': 2,
        'active': 1,
        'title': 'Old argument',
        'content': 'They were furious with each other.',
        'who_remembers': ['Mara', 'Xaden Riorson'],
        'memory_type': 'Conflict',
        'importance': 'Important',
        'current_relevance': 'superseded',
    },
]

world = relevant_world_bible(state['world_bible'], 'What does Xaden say about the archive?', 42)
assert any(x.get('name') == 'Xaden Riorson' for x in world.get('characters', [])), 'main linked character was dropped'
assert any('archive key' in str(x).lower() for x in world.get('secrets_knowledge', [])), 'scoped secret was dropped'

selected = select_memories_for_context(memories, 'archive map', 10)
assert selected and selected[0]['title'] == 'Archive confession', 'critical relevant memory not prioritized'

prompt = build_system_prompt(conversation, state, memories, 'What does Xaden say about the archive?')
for needle in [
    'Xaden Riorson',
    'dryly funny',
    'who_remembers',
    'Mara admitted she had copied the restricted map.',
    'hidden_from',
    'The archive key is hidden beneath the old map.',
    'selected-persona',  # contract wording/source may differ; persona data is still in hard canon below
]:
    if needle == 'selected-persona':
        assert 'dark curls' in prompt and 'scribe' in prompt, 'persona dossier did not reach prompt'
    else:
        assert needle in prompt, f'missing from prompt: {needle}'


# Stress serialization order: giant relevant history must not push the main Character
# dossier or scoped secret beyond the World Bible prompt cap.
stress_state = {**state, 'world_bible': {**state['world_bible']}}
stress_state['world_bible']['history_backstory'] = [
    {'details': f'archive historical dossier {i} ' + ('x' * 420), 'source': 'scenario-template'}
    for i in range(45)
]
stress_prompt = build_system_prompt(conversation, stress_state, memories, 'archive')
assert 'Xaden Riorson' in stress_prompt, 'main character was truncated behind large history'
assert 'dryly funny' in stress_prompt, 'character voice was truncated behind large history'
assert 'The archive key is hidden beneath the old map.' in stress_prompt, 'knowledge boundary was truncated behind large history'

print('PASS context connection regression')

# Indirect user wording must still retrieve knowledge scoped to the character who is
# actually present in the scene. hidden_from is a retrieval signal, not metadata noise.
indirect_state = {**state, 'world_bible': {**state['world_bible']}}
indirect_state['world_bible']['secrets_knowledge'] = [
    {'details': f'Unrelated secret {i}', 'known_by': ['Someone Else'], 'source': 'auto-rp'} for i in range(55)
] + [secret]
indirect_prompt = build_system_prompt(conversation, indirect_state, memories, '*I glance toward him without saying his name.*')
assert 'The archive key is hidden beneath the old map.' in indirect_prompt, 'hidden_from present-character secret was not retrieved from indirect scene context'
assert 'hidden_from' in indirect_prompt and 'Xaden Riorson' in indirect_prompt
