#!/usr/bin/env python3
from pathlib import Path
import collections, re, sys, xml.etree.ElementTree as ET
ROOT = Path(__file__).resolve().parents[1]
errors=[]

# Product naming regression gates.
if ROOT.name != 'LemenzoMessenger':
    errors.append(f'Project folder must be LemenzoMessenger, found {ROOT.name}')
strings_xml = (ROOT/'res/values/strings.xml').read_text()
if '<string name="app_name">Messages</string>' not in strings_xml:
    errors.append('Launcher app name must be Messages')
if '<string name="app_title">Lemenzo Messages</string>' not in strings_xml:
    errors.append('In-app title must be Lemenzo Messages')
bp = (ROOT/'Android.bp').read_text()
if 'name: "LemenzoMessenger"' not in bp:
    errors.append('Soong module must be LemenzoMessenger')
manifest_text = (ROOT/'AndroidManifest.xml').read_text()
if 'package="com.lemenzo.messenger"' not in manifest_text:
    errors.append('Android package must be com.lemenzo.messenger')
for p in ROOT.rglob('*.xml'):
    try: ET.parse(p)
    except Exception as e: errors.append(f'XML parse failed: {p.relative_to(ROOT)}: {e}')
strings=set(); colors=set(); drawables={p.stem for p in (ROOT/'res/drawable').glob('*')}
for p in (ROOT/'res/values').glob('*.xml'):
    for el in ET.parse(p).getroot():
        n=el.attrib.get('name')
        if el.tag=='string' and n: strings.add(n)
        if el.tag=='color' and n: colors.add(n)
for p in ROOT.rglob('*.java'):
    text=p.read_text()
    for kind, defined in [('string', strings), ('color', colors), ('drawable', drawables)]:
        for m in re.finditer(rf'(?<!android\.)R\.{kind}\.([A-Za-z0-9_]+)', text):
            if m.group(1) not in defined: errors.append(f'Missing R.{kind}.{m.group(1)} in {p.relative_to(ROOT)}')
forbidden=['#3268EB','#4A83FF','#2D63E8','#4D88FF','#3F8CFF','#36C98F']
for p in (ROOT/'res').rglob('*.xml'):
    t=p.read_text().lower()
    for c in forbidden:
        if c.lower() in t: errors.append(f'Unlocked color {c} in {p.relative_to(ROOT)}')
for p in ROOT.rglob('*'):
    if p.is_file() and ('.before-' in p.name or p.suffix in {'.bak','.orig'} or p.name.endswith('~')):
        errors.append(f'Backup/source junk: {p.relative_to(ROOT)}')

# Lemenzo Messages conversation-management regression gates.
repo=(ROOT/'src/com/lemenzo/messenger/data/MessageRepository.java').read_text()
conv=(ROOT/'src/com/lemenzo/messenger/ConversationActivity.java').read_text()
resolver=(ROOT/'src/com/lemenzo/messenger/data/ContactResolver.java').read_text()
if 'public boolean deleteMessage(MessageItem message)' not in repo: errors.append('Missing individual message deletion')
if 'public boolean deleteConversation(long threadId)' not in repo: errors.append('Missing conversation deletion')
if 'content://mms-sms/conversations/obsolete' not in repo:
    errors.append('Conversation deletion does not clean obsolete Telephony threads')
if 'Uri.parse("content://mms-sms/conversations")' not in repo:
    errors.append('Conversation deletion does not use unified MMS/SMS conversation provider')
if '"message_count"' not in repo or 'if (c.getInt(5) <= 0) continue;' not in repo:
    errors.append('Conversation list can surface empty orphan threads as Unknown')
if 'addressForThread(threadId)' not in repo: errors.append('Missing phone-number fallback for conversation participant')
if 'confirmDeleteMessage(message)' not in conv or 'confirmDeleteConversation()' not in conv: errors.append('Delete actions are not wired into conversation UI')
if 'ContactsContract.Intents.Insert.PHONE' not in conv: errors.append('Add-contact action does not prefill sender phone number')
if 'contactResolver.contactUri(address)' not in conv: errors.append('Existing contact lookup is not wired')
if 'return "Unknown";' in resolver: errors.append('ContactResolver must not replace an available phone identity with Unknown')

# Swipe/performance regression gates.
swipe=(ROOT/'src/com/lemenzo/messenger/ui/SwipeDeleteTouchListener.java').read_text()
main=(ROOT/'src/com/lemenzo/messenger/MainActivity.java').read_text()
if 'HORIZONTAL_BIAS = 1.25f' not in swipe or 'getScaledTouchSlop()' not in swipe:
    errors.append('Swipe direction lock/touch slop is missing')
if 'requestDisallowInterceptTouchEvent(true)' not in swipe:
    errors.append('Horizontal swipe does not take ownership after direction lock')
if 'drainRefreshRequests' not in main or 'lastRenderSignature' not in main:
    errors.append('Conversation list refresh coalescing/deduplication is missing')
if 'drainRefreshRequests' not in conv or 'cachedMessages' not in conv:
    errors.append('Conversation refresh cache/coalescing is missing')

if errors:
    print('SOURCE VALIDATION: FAIL')
    for e in errors: print('-',e)
    sys.exit(1)
print('SOURCE VALIDATION: PASS')
print('XML/resources/design lock/source hygiene are consistent')

# v5.2 dual-SIM persistence regression gates.
tracker=(ROOT/'src/com/lemenzo/messenger/sms/MultipartSendTracker.java').read_text()
sender=(ROOT/'src/com/lemenzo/messenger/sms/SmsSender.java').read_text()
receiver=(ROOT/'src/com/lemenzo/messenger/sms/SmsDeliverReceiver.java').read_text()
repo=(ROOT/'src/com/lemenzo/messenger/data/MessageRepository.java').read_text()
dual_sim_errors=[]
if 'insertInbox(address, messageBody, time, subscriptionId)' not in receiver:
    dual_sim_errors.append('Incoming SMS does not pass subscription ID to persistence')
if 'MultipartSendTracker.begin(context, token, destination, text, parts.size(), subscriptionId)' not in sender:
    dual_sim_errors.append('Outgoing SMS tracker does not receive subscription ID')
if '.putInt(token + ".subscription_id", subscriptionId)' not in tracker or 'insertSent(address, body, System.currentTimeMillis(), subscriptionId)' not in tracker:
    dual_sim_errors.append('Outgoing SMS does not retain subscription ID through final persistence')
if 'values.put(Telephony.Sms.SUBSCRIPTION_ID, subscriptionId)' not in repo:
    dual_sim_errors.append('SMS provider rows do not persist subscription ID')
if dual_sim_errors:
    print('SOURCE VALIDATION: FAIL')
    for err in dual_sim_errors:
        print(' -', err)
    raise SystemExit(1)

print('- dual-SIM SMS persistence regression')
