# -*- coding: utf-8 -*-
"""
F3 schema-per-tenant: codemod do frontend.

1. supabase.from('<tabela/view tenant>') -> tenantDb().from('...') (84 tabelas + 6 views)
2. injeta import { tenantDb } from '@/lib/supabase/tenantClient'
3. remove .eq('tenant_id', <expr>) APENAS dentro de cadeias tenantDb().from(...)
4. relatorio de sobras pra passada manual:
   - tenant_id em payloads dentro de cadeias tenantDb (insert/upsert/update)
   - onConflict com tenant_id em cadeias tenantDb
   - supabase.from(<nao-literal>) pra auditoria

Uso: python scripts/codemod-tenant-db.py [--apply]   (default: dry-run)
"""
import io, os, re, sys

ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'src')
APPLY = '--apply' in sys.argv

TENANT_RELS = [
    # 84 tabelas (docs/F0_categorizacao.md §1.1 + §1.2)
    'agenda_bloqueios','agenda_configuracoes','agenda_eventos','agenda_online_slots',
    'agenda_regras_semanais','agenda_slots_bloqueados_semanais','agenda_slots_regras',
    'agendador_configuracoes','agendador_solicitacoes','asaas_customers','asaas_payments',
    'billing_contracts','clinical_note_templates','clinical_note_versions','clinical_notes',
    'commitment_services','commitment_time_logs','company_profiles','contact_email_types',
    'contact_emails','contact_phones','contact_types','conversation_assignments',
    'conversation_autoreply_log','conversation_autoreply_settings','conversation_bot_sessions',
    'conversation_bots','conversation_messages','conversation_notes','conversation_optout_keywords',
    'conversation_optouts','conversation_sla_breaches','conversation_sla_rules','conversation_tags',
    'conversation_thread_tags','determined_commitment_fields','determined_commitments',
    'document_access_logs','document_generated','document_share_links','document_signatures',
    'document_templates','documents','email_layout_config','email_templates_tenant','feriados',
    'financial_categories','financial_exceptions','financial_records','insurance_plan_services',
    'insurance_plans','medicos','notification_channels','notification_logs','notification_preferences',
    'notification_queue','notification_schedules','notification_templates','notifications',
    'patient_contacts','patient_discounts','patient_group_patient','patient_groups',
    'patient_intake_requests','patient_invite_attempts','patient_invites','patient_patient_tag',
    'patient_status_history','patient_support_contacts','patient_tags','patient_timeline','patients',
    'payment_settings','professional_pricing','recurrence_exceptions','recurrence_rule_services',
    'recurrence_rules','services','session_reminder_logs','session_reminder_settings',
    'therapist_payout_records','therapist_payouts','twilio_subaccount_usage','whatsapp_connection_incidents',
    # 6 views clonadas por schema
    'conversation_threads','audit_log_unified','v_cashflow_projection','v_commitment_totals',
    'v_patient_groups_with_counts','v_tag_patient_counts',
]
SKIP_FILES = {'tenantClient.js', 'useTenantDb.js', 'client.js'}

names = '|'.join(sorted(TENANT_RELS, key=len, reverse=True))
FROM_RE = re.compile(r"supabase\s*\.\s*from\(\s*(['\"])(" + names + r")\1\s*\)")
IMPORT_LINE = "import { tenantDb } from '@/lib/supabase/tenantClient';"

def skip_string(s, p):
    q = s[p]; p += 1
    while p < len(s):
        if s[p] == '\\': p += 2; continue
        if s[p] == q: return p + 1
        p += 1
    return p

def balanced_end(s, open_paren):
    """indice logo apos o ')' que fecha o '(' em open_paren"""
    depth = 0; p = open_paren
    while p < len(s):
        c = s[p]
        if c in '\'"`':
            p = skip_string(s, p); continue
        if c == '(': depth += 1
        elif c == ')':
            depth -= 1
            if depth == 0: return p + 1
        p += 1
    return p

def chain_end(s, start):
    """fim da cadeia de metodos iniciada logo apos from(...)"""
    i = start
    while True:
        j = i
        while j < len(s) and s[j] in ' \t\r\n': j += 1
        if j < len(s) and s[j] == '.':
            m = re.match(r'[A-Za-z_$][\w$]*', s[j+1:])
            if not m: return i
            k = j + 1 + m.end()
            while k < len(s) and s[k] in ' \t\r\n': k += 1
            if k < len(s) and s[k] == '(':
                i = balanced_end(s, k)
            elif k < len(s) and s[k] == ';':
                return k
            else:
                # acesso a propriedade sem chamada (ex.: .then? sempre tem parens) — para
                return i
        else:
            return i

EQ_RE = re.compile(r"\.\s*eq\(\s*(['\"])tenant_id\1\s*,")

report = {'files': 0, 'from': 0, 'eq': 0, 'payload': [], 'onconflict': [], 'dynamic_from': []}

for dirpath, dirnames, filenames in os.walk(ROOT):
    dirnames[:] = [d for d in dirnames if d not in ('node_modules', '__tests__')]
    for fn in filenames:
        if not fn.endswith(('.js', '.vue', '.ts')) or fn in SKIP_FILES:
            continue
        path = os.path.join(dirpath, fn)
        text = io.open(path, encoding='utf-8').read()
        orig = text

        # 1. from() replacement
        text, n_from = FROM_RE.subn(lambda m: "tenantDb().from(%s%s%s)" % (m.group(1), m.group(2), m.group(1)), text)
        report['from'] += n_from

        # 3. eq removal dentro de cadeias tenantDb
        n_eq = 0
        while True:
            removed = False
            for m in re.finditer(r"tenantDb\(\)\s*\.\s*from\(", text):
                fstart = m.end() - 1
                fend = balanced_end(text, fstart)
                cend = chain_end(text, fend)
                span = text[fend:cend]
                em = EQ_RE.search(span)
                if em:
                    eq_open = fend + span.index('(', em.start() + 1)
                    # acha o '(' do .eq
                    eq_paren = fend + em.end() - len(em.group(0)) + span[em.start():].index('(')
                    eq_paren = fend + em.start() + text[fend + em.start():].index('(')
                    eq_close = balanced_end(text, eq_paren)
                    eq_dot = fend + em.start()
                    text = text[:eq_dot] + text[eq_close:]
                    n_eq += 1
                    removed = True
                    break
            if not removed:
                break
        report['eq'] += n_eq

        # 2. import injection
        if 'tenantDb(' in text and "from '@/lib/supabase/tenantClient'" not in text:
            anchor = re.search(r"^import .*from '@/lib/supabase/client';?\s*$", text, re.M)
            if anchor:
                text = text[:anchor.end()] + '\n' + IMPORT_LINE + text[anchor.end():]
            else:
                first_import = re.search(r"^import .*$", text, re.M)
                if first_import:
                    text = text[:first_import.end()] + '\n' + IMPORT_LINE + text[first_import.end():]
                else:
                    report['payload'].append((path, 0, 'SEM PONTO DE IMPORT — inserir manualmente'))

        # 4. relatorios de sobras dentro de cadeias tenantDb
        for m in re.finditer(r"tenantDb\(\)\s*\.\s*from\(", text):
            fstart = m.end() - 1
            fend = balanced_end(text, fstart)
            cend = chain_end(text, fend)
            span = text[fend:cend]
            line = text[:m.start()].count('\n') + 1
            if re.search(r"\btenant_id\b", span):
                if 'onConflict' in span and 'tenant_id' in span:
                    report['onconflict'].append((path, line))
                else:
                    report['payload'].append((path, line, 'tenant_id na cadeia'))

        # from() dinamico com supabase (auditoria)
        for m in re.finditer(r"supabase\s*\.\s*from\(\s*[^'\")]", text):
            line = text[:m.start()].count('\n') + 1
            report['dynamic_from'].append((path, line))

        if text != orig:
            report['files'] += 1
            if APPLY:
                io.open(path, 'w', encoding='utf-8', newline='').write(text)

mode = 'APPLY' if APPLY else 'DRY-RUN'
print('[%s] arquivos alterados: %d | from substituidos: %d | eq removidos: %d' %
      (mode, report['files'], report['from'], report['eq']))
print('\n-- tenant_id sobrando em cadeias tenantDb (payloads, passada manual): %d' % len(report['payload']))
for p, l, why in report['payload'][:80]:
    print('   %s:%s  (%s)' % (os.path.relpath(p, ROOT), l, why))
print('\n-- onConflict com tenant_id em cadeias tenantDb: %d' % len(report['onconflict']))
for p, l in report['onconflict']:
    print('   %s:%s' % (os.path.relpath(p, ROOT), l))
print('\n-- supabase.from(nao-literal) pra auditoria: %d' % len(report['dynamic_from']))
for p, l in report['dynamic_from'][:40]:
    print('   %s:%s' % (os.path.relpath(p, ROOT), l))
