#!/usr/bin/env python3
"""Offline rules engine for a read-only GA4 property audit (rules G01 to G26).

Usage, from the audit working folder (never write into the skill folder):
  python3 <skill folder>/scripts/audit_ga4.py --config audit-config.json [--snapshot ga4-snapshot.json]
      [--ui ui-settings.csv] [--exports FILE_OR_DIR ...] [--tag-coverage coverage.csv]
      [--bq bq-results.json] [--ads ads-conversions.json] [--as-of YYYY-MM-DD] [--json] > report.md
  python3 <skill folder>/scripts/audit_ga4.py --self-test

Reads local files only. It never calls an API and never writes to GA4. The rules, input readers
and report are in scripts/lib/; this file is the command line.
Input shapes: references/reading-sources.md and references/data-checks.md.
Accepts REST camelCase and MCP snake_case keys. Raw transaction ID, PII probe and custom
dimension rows are reduced to counts in memory; their values are never printed. Count-only
shapes from route A (data-checks.md) are read as they are.
A rule PASSes only when every primary input it needs was read; otherwise it is NOT CHECKED.
Exit code: 1 when any rule FAILs, 2 on an input error, 0 otherwise.
Standard library only, Python 3.9 or later.
"""
import argparse
import json
import os
import re
import sys

sys.dont_write_bytecode = True  # never leave __pycache__ in the skill folder
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from lib.common import EXPORTS, todate  # noqa: E402
from lib.context import Ctx  # noqa: E402
from lib.inputs import load_object, parse_export, parse_tagcov, read_ui  # noqa: E402
from lib.report import as_json, render  # noqa: E402
from lib.scoring import run_rules, score  # noqa: E402
from lib.selftest import self_test  # noqa: E402

def main(argv=None):
    ap = argparse.ArgumentParser(description='Offline GA4 audit rules engine (G01 to G26). Reads files only.')
    ap.add_argument('--config')
    ap.add_argument('--snapshot')
    ap.add_argument('--ui')
    ap.add_argument('--exports', nargs='+', default=[])
    ap.add_argument('--tag-coverage')
    ap.add_argument('--bq')
    ap.add_argument('--ads')
    ap.add_argument('--as-of')
    ap.add_argument('--json', action='store_true')
    ap.add_argument('--self-test', action='store_true')
    a = ap.parse_args(argv)
    if a.self_test:
        return self_test(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'assets', 'regression-snapshot.json'))
    if not a.config:
        print('Input error: --config is required (see assets/audit-config-template.json)', file=sys.stderr)
        return 2
    if a.as_of and not todate(a.as_of):
        print('Input error: --as-of must be YYYY-MM-DD', file=sys.stderr)
        return 2
    try:
        cfg = load_object(a.config, '--config')
        snap = load_object(a.snapshot, '--snapshot') if a.snapshot else {}
        exports = {}
        for p in a.exports:
            for fp in sorted(os.path.join(p, x) for x in os.listdir(p) if x.lower().endswith('.csv')) if os.path.isdir(p) else [p]:
                with open(fp, encoding='utf-8-sig') as fh:
                    text = fh.read()
                found = parse_export(text)
                if not found:
                    first = next((x for x in text.splitlines() if x.strip() and not x.lstrip().startswith('#')), '')
                    shown = first[:160] if not re.search(r'@|%40|://|=', first) else '(hidden: it may hold URL values)'
                    print(f'Input error: no known GA4 export table in {fp}. First non-comment line: {shown}. '
                          'Expected English column labels: ' + '; '.join(', '.join(r) for _, r in EXPORTS)
                          + '. Rename the columns of a non-English export to these labels.', file=sys.stderr)
                    return 2
                exports.update(found)
        tc = None
        if a.tag_coverage:
            with open(a.tag_coverage, encoding='utf-8-sig') as fh:
                tc = parse_tagcov(fh.read())
        c = Ctx(snap, cfg, read_ui(a.ui) if a.ui else {}, load_object(a.bq, '--bq') if a.bq else None,
                load_object(a.ads, '--ads') if a.ads else None, exports, tc, a.as_of)
    except (OSError, ValueError, StopIteration, KeyError) as e:
        print(f'Input error: {type(e).__name__}: {e}', file=sys.stderr)
        return 2
    fs = run_rules(c)
    sc = score(fs)
    used = [n for n, v in (('snapshot', a.snapshot), ('config', a.config), ('UI intake', a.ui), ('exports', a.exports),
                           ('Tag coverage CSV', a.tag_coverage), ('BigQuery results', a.bq), ('Google Ads results', a.ads)) if v]
    print(json.dumps(as_json(c, fs, sc), indent=2, default=str) if a.json else render(c, fs, sc, ', '.join(used)), end='' if not a.json else '\n')
    return 1 if any(f.status == 'FAIL' for f in fs) else 0

if __name__ == '__main__': sys.exit(main())
