#!/usr/bin/env python3
from pathlib import Path
import re
import sys

if len(sys.argv) != 3:
    raise SystemExit('usage: patch_index_v249389.py index.html index.js')
html_path = Path(sys.argv[1])
js_path = Path(sys.argv[2])

js = js_path.read_text(encoding='utf-8', errors='strict')
lines = js.splitlines()

def replace_line(prefix: str, replacement: str) -> None:
    indexes = [i for i, line in enumerate(lines) if line.startswith(prefix)]
    if len(indexes) != 1:
        raise SystemExit(f'ERROR: {prefix!r} count={len(indexes)}')
    lines[indexes[0]] = replacement

replace_line(
    'async function loadMessages(reset=false)',
    "async function loadMessages(reset=false){if(!state.activeChannel)return;const box=$('messages');if(!box)return;try{const secure=String(state.activeChannel||'').startsWith('dm:')&&window.ITHAKI_SECURE_V249389;const path=secure?window.ITHAKI_SECURE_V249389.messagesPath(state.activeChannel,reset?0:state.messagesSince,300):`/api/messages?room_id=${encodeURIComponent(state.room)}&channel=${encodeURIComponent(state.activeChannel)}&since=${reset?0:state.messagesSince}&limit=300`;const r=await fetchJson(path);const items=uniqueMessages(r.messages||[]);const oldSuppress=state.suppressMessageAlerts;if(reset){box.innerHTML='';state.lastMsgIds.clear();state.suppressMessageAlerts=true}items.forEach(appendMessage);state.suppressMessageAlerts=oldSuppress;if(items.length){state.messagesSince=Math.max(...items.map(x=>+x.created_at||0),state.messagesSince);box.scrollTop=box.scrollHeight;await markActiveChannelRead();await loadReadState(state.activeChannel)}if(!box.children.length)box.innerHTML='<div class=\"card\">Start the conversation.</div>'}catch(e){state.suppressMessageAlerts=false;if(reset)box.innerHTML=`<div class=\"card\">Messages are not loading: ${esc(e.message)}</div>`}}"
)

replace_line(
    'function appendMessage(m)',
    "function appendMessage(m){const box=$('messages');if(!box||state.lastMsgIds.has(m.message_id))return;state.lastMsgIds.add(m.message_id);const mine=String(m.sender_id)===state.device;let t=parseText(m.text);const number=box.querySelectorAll?box.querySelectorAll('.bubble').length+1:state.lastMsgIds.size;const div=document.createElement('div');div.className='bubble '+(mine?'mine':'');div.dataset.ts=String(m.created_at||0);if(m.is_encrypted){t={body:'🔐 Decrypting...',media:null};e2eDecryptText(m.iv,m.cipher_text,m.channel||state.activeChannel).then(txt=>{const body=div.querySelector('.msgBody');if(body)body.textContent=txt})}const receipt=mine?`<span class=\"readReceipt\" data-ts=\"${esc(String(m.created_at||0))}\">✓ στάλθηκε</span>`:'';div.innerHTML=`<div class=\"msgNo\">#${number}</div><div class=\"msgBody\">${esc(t.body)}</div>${translateControls('msg_'+esc(m.message_id||number),t.body)}${messageMediaHtml(t.media)}<div class=\"meta\">${fmt(m.created_at)} ${receipt} <button class=\"icon small\" onclick=\"ITH.replyMsg('${esc(m.message_id)}','${esc((t.body||'').slice(0,60))}')\">↩</button> ${mine?`<button class=\"icon small\" onclick=\"ITH.deleteMsg('${esc(m.message_id)}')\">🗑️</button>`:''}</div>`;box.appendChild(div);try{window.ITHAKI_SECURE_V249389&&window.ITHAKI_SECURE_V249389.onMessageRendered(m,div)}catch(e){}maybeAutoTranslate('msg_'+(m.message_id||number),t.body);if(mine)updateReadReceipts(state.activeChannel);if(!mine){markActiveChannelRead();notifyIncomingMessage(m,t.body);if(String(m.sender_id)===String(state.activePeer))markPeerRead(m.sender_id);else{addNotice({type:'message',from_id:m.sender_id,from_name:m.nickname||'Member',text:uiText('New message')+' '+(m.nickname||'Member')});refreshNavBadges()}}}"
)

replace_line(
    'async function e2eDecryptText(iv,cipher,channel)',
    "async function legacyE2EDecryptTextV249389(iv,cipher,channel){const pass=localStorage.getItem(e2eKeyName(channel));if(!pass||!crypto?.subtle)return '';try{const key=await e2eImportKey(pass);const plain=await crypto.subtle.decrypt({name:'AES-GCM',iv:fromB64(iv)},key,fromB64(cipher));return new TextDecoder().decode(plain)}catch{return '🔐 Encrypted message · wrong or missing key'}}\nasync function e2eDecryptText(iv,cipher,channel){if(window.ITHAKI_SECURE_V249389)return window.ITHAKI_SECURE_V249389.decryptText(iv,cipher,channel,legacyE2EDecryptTextV249389);return legacyE2EDecryptTextV249389(iv,cipher,channel)}"
)

old_open = next((line for line in lines if line.startswith('function openE2EPanel(channel)')), None)
if old_open is None:
    raise SystemExit('ERROR: openE2EPanel not found')
# Keep the legacy shared-key panel for communities, but use the native secure panel for direct messages.
legacy_body = old_open[len('function openE2EPanel(channel){'):]
replace_line(
    'function openE2EPanel(channel)',
    "function openE2EPanel(channel){const secureCh=channel||state.activeChannel||state.currentSpaceChannel||'';if(String(secureCh).startsWith('dm:')){if(window.ITHAKI_SECURE_V249389)return window.ITHAKI_SECURE_V249389.openPanel(secureCh);toast('Η ασφαλής κλειδαριά V249389 δεν φορτώθηκε.');return false}" + legacy_body
)

replace_line(
    'async function sendMessagePayload(channel,payload)',
    "async function sendMessagePayload(channel,payload){if(!channel)throw new Error('no channel');if(String(channel).startsWith('dm:')){if(!window.ITHAKI_SECURE_V249389)throw new Error('Η ασφαλής λειτουργία V249389 δεν φορτώθηκε.');await window.ITHAKI_SECURE_V249389.sendMessage(channel,payload);wsSubscribe(channel);return}const enc=e2eEnabled(channel)?await e2eEncryptText(payload,channel):null;const body=enc?{room_id:state.room,channel,sender_id:state.device,nickname:state.name,language:'en',text:'',is_encrypted:true,iv:enc.iv,cipher_text:enc.cipher_text}:{room_id:state.room,channel,sender_id:state.device,nickname:state.name,language:'en',text:payload};await fetchJson('/api/messages/send',{method:'POST',body:JSON.stringify(body)});wsSubscribe(channel)}"
)

js = '\n'.join(lines) + '\n'
checks = {
    'ITHAKI_SECURE_V249389.messagesPath': 1,
    'ITHAKI_SECURE_V249389.onMessageRendered': 1,
    'legacyE2EDecryptTextV249389': 3,
    'ITHAKI_SECURE_V249389.openPanel': 1,
    'ITHAKI_SECURE_V249389.sendMessage': 1,
}
for marker, expected in checks.items():
    actual = js.count(marker)
    if actual != expected:
        raise SystemExit(f'ERROR: {marker} count={actual}, expected={expected}')
if "fetchJson('/api/messages/send'" not in js:
    raise SystemExit('ERROR: non-DM legacy message send path removed')
js_tmp = js_path.with_suffix(js_path.suffix + '.v249389.tmp')
js_tmp.write_text(js, encoding='utf-8')
js_tmp.replace(js_path)

html = html_path.read_text(encoding='utf-8', errors='strict')
# Remove only V249389 block if rerun, then refresh all cache markers from the V249388 base.
html = re.sub(r'\s*<!-- V249389_SECURE_DM_ANDROID_FOUNDATION_FULL -->[\s\S]*?<script[^>]+secure_messaging_v249389\.js[^>]*>\s*</script>\s*', '\n', html, flags=re.I)
html = html.replace('index.js?v=249388-creator-tools-direct-visible', 'index.js?v=249389-secure-dm-android-foundation')
html = html.replace('v=249388-direct-visible', 'v=249389-secure-dm-android-foundation')
html = html.replace('content="V249388_CREATOR_TOOLS_DIRECT_VISIBLE_FULL"', 'content="V249389_SECURE_DM_ANDROID_FOUNDATION_FULL"')
block = '''
<!-- V249389_SECURE_DM_ANDROID_FOUNDATION_FULL -->
<script src="/js/secure_messaging_v249389.js?v=249389-secure-dm-android-foundation" charset="utf-8" defer></script>
'''
pos = html.lower().rfind('</body>')
if pos < 0:
    raise SystemExit('ERROR: </body> not found')
html = html[:pos] + block + html[pos:]
if html.count('secure_messaging_v249389.js') != 1:
    raise SystemExit('ERROR: secure messaging script count')
if html.count('V249389_SECURE_DM_ANDROID_FOUNDATION_FULL') != 2:
    raise SystemExit('ERROR: V249389 build marker count')
if html.count('index.js?v=249389-secure-dm-android-foundation') != 1:
    raise SystemExit('ERROR: V249389 index cache marker')
html_tmp = html_path.with_suffix(html_path.suffix + '.v249389.tmp')
html_tmp.write_text(html, encoding='utf-8')
html_tmp.replace(html_path)

print('INDEX_HTML_PATCH=V249389')
print('DM_SEND_USES_ANDROID_KEYSTORE_E2EE=YES')
print('DM_LOAD_USES_SECURE_ENDPOINT=YES')
print('DM_LOCK_USES_NATIVE_SECURITY_PANEL=YES')
print('LEGACY_GROUP_E2E_PRESERVED=YES')
