#!/usr/bin/env python3
"""
G5 - archives the D7 datasets that have no D10 destination (decision 12).

READ-ONLY against D7. Same export and verification method as
archive_commerce_revisions.py; see that file for the encoding notes.

WHAT IS HERE AND WHY

  license_number_update      4,600   Audit trail of a one-time licence-prefix
                                     backfill. DATABASE_ANALYSIS.md 2.6: "no
                                     hook_schema anywhere ... nothing on D10
                                     will ever recreate this table. It is pure
                                     data artifact - migrate it explicitly or
                                     archive it deliberately." Its `id` column
                                     is the user's uid, not a serial. Nothing
                                     in the D10 codebase references it.

  commerce_abandoned_carts   3,915   Which carts were sent an abandoned-cart
                                     notification, and when. D10 has no table
                                     and no code that reads one; the
                                     abandoned-cart EMAIL was migrated as a
                                     template only. Historical record.

NOT HERE, AND THIS IS THE POINT: commerce_addressbook_defaults was the third
undecided dataset and it is NOT archived, because it turned out to have a live
D10 destination - Commerce 3's profile.is_default. It was MIGRATED instead, by
scripts/reconcile/restore_profile_ownership.php. Archiving it would have
preserved the bytes and lost the behaviour.
"""
import csv, gzip, json, subprocess, sys, os, hashlib, datetime

D7 = '/Users/apple/neerja/ceonline'
OUT = '/Users/apple/neerja/ceonline-d10/archive/d7-datasets'
TABLES = ['license_number_update', 'commerce_abandoned_carts']

def unescape(v):
    if v == 'NULL':
        return None
    out=[]; i=0
    while i < len(v):
        c=v[i]
        if c=='\\' and i+1 < len(v):
            out.append({'t':'\t','n':'\n','0':'\0','\\':'\\'}.get(v[i+1], v[i+1])); i+=2
        else:
            out.append(c); i+=1
    return ''.join(out)

manifest={'generated':datetime.datetime.now().isoformat(timespec='seconds'),
          'source':'ceonline (Drupal 7.105), read-only',
          'decision':'Decision 12 - preserve; archive rather than delete.',
          'tables':[]}

for t in TABLES:
    n = int(subprocess.run(['ddev','mysql','--batch','--skip-column-names','-e',
        f'SELECT COUNT(*) FROM `{t}`'], cwd=D7, capture_output=True, text=True).stdout.strip())
    cols = [c for c in subprocess.run(['ddev','mysql','--batch','--skip-column-names','-e',
        f'SHOW COLUMNS FROM `{t}`'], cwd=D7, capture_output=True, text=True).stdout.strip().split('\n') if c]
    cols = [c.split('\t')[0] for c in cols]
    path = os.path.join(OUT, f'{t}.csv.gz')
    p = subprocess.Popen(['ddev','mysql','--batch','-e',f'SELECT * FROM `{t}`'],
                         cwd=D7, stdout=subprocess.PIPE, text=True, bufsize=1<<20)
    written=0; header=None; seen=set(); dupes=0
    with gzip.open(path,'wt',newline='',encoding='utf-8') as fh:
        w=csv.writer(fh)
        for line in p.stdout:
            line=line.rstrip('\n')
            if header is None:
                header=line.split('\t'); w.writerow(header); continue
            row=[unescape(f) for f in line.split('\t')]
            row=['' if f is None else f for f in row]
            key=tuple(row)
            if key in seen: dupes+=1
            seen.add(key)
            w.writerow(row); written+=1
    p.wait()
    if header is None:
        with gzip.open(path,'wt',newline='',encoding='utf-8') as fh:
            csv.writer(fh).writerow(cols)
        header=cols
    ok = (written==n)
    manifest['tables'].append({'table':t,'source_rows':n,'archived_rows':written,'match':ok,
        'duplicate_rows':dupes,'columns':header,'file':os.path.basename(path),
        'bytes':os.path.getsize(path),
        'sha256_of_file':hashlib.sha256(open(path,'rb').read()).hexdigest()})
    print(f"  {t:30} {n:>7,} -> {written:>7,}  {'OK' if ok else 'MISMATCH'}  dupes={dupes}  {os.path.getsize(path)/1024:.0f} KB", flush=True)
    if not ok:
        print("  ABORT: row count mismatch", file=sys.stderr); sys.exit(1)

manifest['total_source_rows']=sum(x['source_rows'] for x in manifest['tables'])
manifest['total_archived_rows']=sum(x['archived_rows'] for x in manifest['tables'])
json.dump(manifest, open(os.path.join(OUT,'MANIFEST.json'),'w'), indent=2)
open(os.path.join(OUT,'SHA256SUMS'),'w').write(''.join(f"{x['sha256_of_file']}  {x['file']}\n" for x in manifest['tables']))
print(f"\nTOTAL {manifest['total_source_rows']:,} -> {manifest['total_archived_rows']:,} archived")
