from __future__ import annotations
import os
os.environ['MELUMIYACHAT_CLOUD_ENABLED']='0'
os.environ['MELUMIYACHAT_LOCAL_REAL_MODELS']='0'

from fastapi.testclient import TestClient
from app.main import app
from app import db

c=TestClient(app)
# Isolated RP with explicit context sentinel.
cid=db.execute("INSERT INTO conversations(title,persona_name,scenario_name,canon_mode,reply_length) VALUES (?,?,?,?,?)",('E2E audit','Mara','Audit Scenario','Selective Canon','Short'))
try:
    docs=db.default_state_docs()
    docs['hard_canon']['user_persona']={'name':'Mara','occupation':'scribe','appearance':'dark curls','source':'selected-persona-snapshot'}
    docs['world_bible']={
        'schema_version':4,'overview':{'name':'Audit World','description':'E2E'},
        'history_backstory':[],'background_facts':[],'characters':[{
            'name':'Xaden Riorson','role':'main character','source':'scenario-template-linked-character',
            'definition':{'personality_profile':{'traits':['dryly funny']},'rp_behavior':{'speech_style':'terse'}}
        }],
        'relationship_history':[],'core_canon':[],'locations':[],
        'secrets_knowledge':[{'details':'E2E_SECRET: the key is under the map','known_by':['Mara'],'hidden_from':['Xaden Riorson'],'source':'user-created'}],
        'groups_factions':[],'important_objects':[],'world_rules':[],'current_state':[],
        'physical_continuity':[],'story_timeline':[],'custom_entries':[],'auto_review_conflicts':[]
    }
    docs['knowledge']['facts']=[{'fact':'E2E_SECRET: the key is under the map','known_by':['Mara'],'hidden_from':['Xaden Riorson'],'source':'user-created'}]
    for k,v in docs.items(): db.update_state(cid,k,v)
    c.post('/api/memories',json={'conversation_id':cid,'title':'E2E memory','what_happened':'E2E_MEMORY: Mara hid the key yesterday.','who_remembers':['Mara'],'who_involved':['Mara'],'memory_type':'Secret Learned','importance':'Critical','current_relevance':'active','source_kind':'User-created'})

    first=c.post('/api/chat',json={'conversation_id':cid,'content':'I look at Xaden and ask about the map.','model_key':'qwen3-free'})
    assert first.status_code==200, first.text
    data=first.json(); assert data['model_key']=='qwen3-free', data
    aid=data['message']['id']; assert data['message']['is_selected']==1

    reroll=c.post('/api/chat',json={'conversation_id':cid,'content':'','model_key':'qwen3-free','reroll_of':aid})
    assert reroll.status_code==200, reroll.text
    rid=reroll.json()['message']['id']; assert reroll.json()['message']['is_selected']==0
    selected=c.post('/api/rerolls/select',json={'conversation_id':cid,'message_id':rid})
    assert selected.status_code==200, selected.text
    sb=selected.json(); assert sb['selected_message_id']==rid and 'rollback' in sb, sb
    rows=db.rows("SELECT id,is_selected FROM messages WHERE conversation_id=? AND turn_group=(SELECT turn_group FROM messages WHERE id=?) AND role='assistant' ORDER BY id",(cid,rid))
    assert sum(int(x['is_selected']) for x in rows)==1, rows
    assert next(x for x in rows if x['id']==rid)['is_selected']==1

    # User-authored memory and scoped knowledge survive model generation/reroll switch.
    mem=db.row("SELECT * FROM memories WHERE conversation_id=? AND title='E2E memory'",(cid,))
    assert mem and mem['source_kind']=='User-created'
    state=db.get_state(cid)
    assert any('E2E_SECRET' in str(x) for x in state['knowledge']['facts'])
    assert any('hidden_from' in str(x) for x in state['world_bible']['secrets_knowledge'])
    print('PASS — end-to-end local RP/model/reroll/context loop')
finally:
    db.execute('DELETE FROM conversations WHERE id=?',(cid,))
