#!/usr/bin/env python3
from pathlib import Path
import shutil, sys
root=Path(sys.argv[1]) if len(sys.argv)>1 else Path.cwd()
front=root/'frontend'
src=Path(__file__).resolve().parent
# Copy hard owner JS from this component package.
if (src/'hard_preview_contact_v249402.js').is_file():
    (front/'js').mkdir(parents=True, exist_ok=True)
    shutil.copy2(src/'hard_preview_contact_v249402.js', front/'js/hard_preview_contact_v249402.js')
# Ensure script tag in index.html.
html=front/'index.html'
s=html.read_text(encoding='utf-8')
s=s.replace('249401-immediate-file-preview','249402-hard-preview-contact-clean')
if 'hard_preview_contact_v249402.js' not in s:
    tag='<script src="/js/hard_preview_contact_v249402.js?v=249402-hard-preview-contact-clean"></script>\n'
    s=s.replace('</body>', tag+'</body>') if '</body>' in s else s+'\n'+tag
html.write_text(s,encoding='utf-8')
# Patch server routes and health/version marker.
relay=root/'relay_server_clean.py'
r=relay.read_text(encoding='utf-8')
r=r.replace('2.4.9.401-immediate-file-preview','2.4.9.402-hard-preview-contact-clean')
r=r.replace('249401-immediate-file-preview','249402-hard-preview-contact-clean')
if 'BEGIN V249402_HARD_PREVIEW_CONTACT_CLEAN' not in r:
    r += '\n' + '# BEGIN V249402_HARD_PREVIEW_CONTACT_CLEAN\n# Server-authoritative friends and hard preview diagnostics. No database/media/message/call deletion.\ndef _v249402_pair(a: str, b: str) -> tuple[str, str]:\n    a=str(a or \'\')[:120]; b=str(b or \'\')[:120]\n    return tuple(sorted([a,b]))\n\ndef _v249402_dedupe_social(room_id: str = \'ithaki-phone-test\') -> dict:\n    init_social_v249223_db()\n    room_id=str(room_id or \'ithaki-phone-test\')[:120]\n    deleted_empty=deleted_duplicate_accepted=deleted_pending_after_accepted=deleted_duplicate_pending=0\n    try:\n        cur=execute("DELETE FROM social_events WHERE room_id=? AND type=\'contact\' AND (from_id=\'\' OR to_id=\'\' OR from_id=to_id)", (room_id,))\n        deleted_empty=int(cur.rowcount if cur.rowcount is not None else 0)\n    except Exception:\n        LOG.exception(\'V249402 empty/self contact cleanup failed\')\n    try:\n        accepted=rows("SELECT id,from_id,to_id,updated_at,created_at FROM social_events WHERE room_id=? AND type=\'contact\' AND status=\'accepted\' ORDER BY updated_at DESC, created_at DESC", (room_id,))\n        seen=set(); drop=[]\n        for r in accepted:\n            a=str(r[\'from_id\'] or \'\'); b=str(r[\'to_id\'] or \'\')\n            if not a or not b or a==b: continue\n            key=_v249402_pair(a,b)\n            if key in seen: drop.append(str(r[\'id\']))\n            else: seen.add(key)\n        for x in drop:\n            execute("DELETE FROM social_events WHERE id=?", (x,)); deleted_duplicate_accepted+=1\n        for a,b in seen:\n            cur=execute("DELETE FROM social_events WHERE room_id=? AND type=\'contact\' AND status=\'pending\' AND ((from_id=? AND to_id=?) OR (from_id=? AND to_id=?))", (room_id,a,b,b,a))\n            deleted_pending_after_accepted += int(cur.rowcount if cur.rowcount is not None else 0)\n    except Exception:\n        LOG.exception(\'V249402 accepted/pending cleanup failed\')\n    try:\n        pending=rows("SELECT id,from_id,to_id,updated_at,created_at FROM social_events WHERE room_id=? AND type=\'contact\' AND status=\'pending\' ORDER BY updated_at DESC, created_at DESC", (room_id,))\n        seen=set(); drop=[]\n        for r in pending:\n            key=_v249402_pair(str(r[\'from_id\'] or \'\'),str(r[\'to_id\'] or \'\'))\n            if not key[0] or not key[1] or key[0]==key[1]: continue\n            if key in seen: drop.append(str(r[\'id\']))\n            else: seen.add(key)\n        for x in drop:\n            execute("DELETE FROM social_events WHERE id=?", (x,)); deleted_duplicate_pending+=1\n    except Exception:\n        LOG.exception(\'V249402 duplicate pending cleanup failed\')\n    return {\'ok\': True, \'room_id\': room_id, \'deleted_empty_or_self\': deleted_empty, \'deleted_duplicate_accepted\': deleted_duplicate_accepted, \'deleted_pending_after_accepted\': deleted_pending_after_accepted, \'deleted_duplicate_pending\': deleted_duplicate_pending, \'data_deleted\': \'NO_MEDIA_MESSAGES_CALLS_PROFILES\'}\n\n@app.get(\'/api/social/member_lists/v249402\')\ndef api_social_member_lists_v249402(room_id: str = Query(\'ithaki-phone-test\'), device_id: str = Query(\'\')):\n    init_social_v249223_db()\n    try: _v249402_dedupe_social(room_id)\n    except Exception: LOG.exception(\'V249402 member list cleanup skipped\')\n    device_id=str(device_id or \'\')[:120]\n    friends=_friend_rows_v249223(str(room_id or \'ithaki-phone-test\')[:120], device_id) if device_id else []\n    return {\'ok\': True, \'version\': \'V249402-hard-preview-contact-clean\', \'source\': \'server-accepted friends only\', \'followers\': [], \'following\': [], \'friends\': friends, \'counts\': {\'followers\': 0, \'following\': 0, \'friends\': len(friends)}}\n\n@app.get(\'/api/social/friend/state_v249402\')\ndef api_social_friend_state_v249402(room_id: str = Query(\'ithaki-phone-test\'), device_id: str = Query(\'\'), peer_id: str = Query(\'\')):\n    init_social_v249223_db()\n    try: _v249402_dedupe_social(room_id)\n    except Exception: pass\n    return {\'ok\': True, \'version\': \'V249402-hard-preview-contact-clean\', \'room_id\': room_id, \'device_id\': device_id, \'peer_id\': peer_id, \'friends\': bool(_are_friends_v249227(room_id, device_id, peer_id)), \'source\': \'same gate as messages and calls\'}\n\n@app.post(\'/api/social/friend/request_v249402\')\ndef api_social_friend_request_v249402(payload: dict):\n    room_id=str((payload or {}).get(\'room_id\') or \'ithaki-phone-test\')[:120]\n    from_id=str((payload or {}).get(\'from_id\') or \'\')[:120]; to_id=str((payload or {}).get(\'to_id\') or \'\')[:120]\n    if from_id and to_id and _are_friends_v249227(room_id, from_id, to_id):\n        clean=_v249402_dedupe_social(room_id)\n        return {\'ok\': True, \'version\': \'V249402-hard-preview-contact-clean\', \'status\': \'accepted\', \'already_friends\': True, \'cleanup\': clean}\n    res=api_social_friend_request(payload or {})\n    clean=_v249402_dedupe_social(room_id)\n    if isinstance(res, dict):\n        res[\'version\']=\'V249402-hard-preview-contact-clean\'; res[\'cleanup\']=clean\n    return res\n\n@app.post(\'/api/social/friend/respond_v249402\')\ndef api_social_friend_respond_v249402(payload: dict):\n    room_id=str((payload or {}).get(\'room_id\') or \'ithaki-phone-test\')[:120]\n    res=api_social_friend_respond_v249240(payload or {})\n    clean=_v249402_dedupe_social(room_id)\n    if isinstance(res, dict):\n        res[\'version\']=\'V249402-hard-preview-contact-clean\'; res[\'cleanup\']=clean; res[\'server_state\']=\'deduped-after-respond\'\n    return res\n\n@app.post(\'/api/admin/repair_contacts_v249402\')\ndef api_admin_repair_contacts_v249402(payload: dict):\n    room_id=str((payload or {}).get(\'room_id\') or \'ithaki-phone-test\')[:120]\n    return _v249402_dedupe_social(room_id)\n\n@app.get(\'/api/diagnostics/v249402\')\ndef api_diagnostics_v249402(room_id: str = Query(\'ithaki-phone-test\')):\n    clean=_v249402_dedupe_social(room_id)\n    total=0; accepted=0; pending=0\n    try:\n        total=int(rows("SELECT COUNT(*) c FROM social_events WHERE room_id=? AND type=\'contact\'", (room_id,))[0][\'c\'])\n        accepted=int(rows("SELECT COUNT(*) c FROM social_events WHERE room_id=? AND type=\'contact\' AND status=\'accepted\'", (room_id,))[0][\'c\'])\n        pending=int(rows("SELECT COUNT(*) c FROM social_events WHERE room_id=? AND type=\'contact\' AND status=\'pending\'", (room_id,))[0][\'c\'])\n    except Exception: pass\n    return {\'ok\': True, \'version\': \'V249402-hard-preview-contact-clean\', \'server_only_friends\': True, \'local_stale_contacts_ignored\': True, \'contact_rows\': {\'total\': total, \'accepted\': accepted, \'pending\': pending}, \'cleanup\': clean}\n\n@app.get(\'/api/diagnostics/file_preview_v249402\')\ndef api_file_preview_v249402():\n    return {\'ok\': True, \'version\': \'V249402-hard-preview-contact-clean\', \'file_selection_hard_owner\': True, \'preview_immediate_lightweight\': True, \'apk_required_for_this_fix\': False}\n# END V249402_HARD_PREVIEW_CONTACT_CLEAN' + '\n'
relay.write_text(r,encoding='utf-8')
print('PATCH_SERVER_V249402=OK')
