"""Input readers and probe reduction: JSON, the intake CSV, GA4 export tables, Tag coverage."""
import csv
import json
from .common import EXPORTS, LABELS, METRICS, get, num, table
from .pii import EMAIL_RE, PHONE_RE, reduce_url

def reduce_txn(rows):
    out = {'purchases': 0, 'missing_ids': 0, 'distinct_ids': 0, 'ids_on_more_than_one_event': 0}
    for r in rows:
        n, t = num(r.get('eventCount')), str(r.get('transactionId', '')).strip()
        out['purchases'] += n
        if t in ('', '(not set)'): out['missing_ids'] += n
        else:
            out['distinct_ids'] += 1
            out['ids_on_more_than_one_event'] += n > 1
    return out

def reduce_dim(r28, r1):
    rows28, rows1 = table(r28) or [], table(r1) or []
    vals = [str(x.get(k, '')) for x in rows28 for k in x if k.startswith('custom')]
    total = sum(x.get('eventCount', 0) for x in rows28)
    notset = sum(x.get('eventCount', 0) for x in rows28 if any(x.get(k) in ('(not set)', '') for k in x if k.startswith('custom')))
    return {'rows_28d': len(rows28), 'not_set_share_28d': notset / total if total else 1.0,
            'distinct_1d': int(num(get(r1, 'rowCount', len(rows1)))),
            'email_like': sum(bool(EMAIL_RE.search(v)) for v in vals),
            'phone_like': sum(bool(PHONE_RE.match(v.strip())) for v in vals)}

def norm_reduced(rid, rep):
    if rep is None or '_table' in rep and rid not in ('R08', 'R15'): return rep
    if rid == 'R08':
        if 'purchases' in rep: return rep
        return dict(reduce_txn(table(rep) or []), metadata=get(rep, 'metadata', {}))
    if rid == 'R15':
        rows = rep.get('rows')
        if isinstance(rows, list) and not get(rep, 'dimensionHeaders') and all(isinstance(x, dict) and 'host' in x for x in rows):
            return rep  # already reduced: script output or a count-only shape
        parts = {'pageLocation': rep} if ('_table' in rep or get(rep, 'dimensionHeaders')) else \
            {k: v for k, v in rep.items() if isinstance(v, dict)}
        out = []
        for field, part in parts.items():
            for x in table(part) or []:
                u = x.get(field) or x.get('pageLocation') or x.get('pageReferrer') or ''
                red = reduce_url(str(u), field, x.get('eventCount', 0))
                if red: out.append(red)
        return {'rows': out}
    if rid == 'R14':
        return {k: (reduce_dim(v.get('report_28d'), v.get('report_1d')) if 'report_28d' in v else v)
                for k, v in rep.items() if isinstance(v, dict)}
    return rep

def load_json(path):
    with open(path, encoding='utf-8') as fh:
        return json.load(fh)

def load_object(path, what):
    """A JSON input that must be one object: a list or a bare value is the wrong file, not an empty one."""
    data = load_json(path)
    if not isinstance(data, dict):
        raise ValueError(f'{what} {path} is not a JSON object (got {type(data).__name__}); see references/data-checks.md for its shape')
    return data

def read_ui(path=None, rows=None):
    out = {}
    if path:
        with open(path, encoding='utf-8-sig', newline='') as fh:
            rows = [r for r in csv.DictReader(fh)]
    for r in rows or []:
        if isinstance(r, (list, tuple)): r = {'setting_id': r[0], 'value': r[1], 'evidence': r[2] if len(r) > 2 else ''}
        k = (r.get('setting_id') or '').strip()
        if k and not k.startswith('#'):
            out[k] = {x: (r.get(x) or '').strip() for x in ('value', 'evidence', 'read_by', 'read_at', 'notes')}
    return out

def parse_export(text):
    """Finds GA4 interface export tables by their English column labels."""
    found, lines, i = {}, text.splitlines(), 0
    while i < len(lines):
        raw = lines[i]
        i += 1
        if not raw.strip() or raw.lstrip().startswith('#'): continue
        head = [h.strip().lower() for h in next(csv.reader([raw]))]
        rid = next((r for r, req in EXPORTS if all(x in head for x in req)), None)
        if not rid: continue
        rows = []
        while i < len(lines) and lines[i].strip() and not lines[i].lstrip().startswith('#'):
            vals = next(csv.reader([lines[i]]))
            i += 1
            if vals and vals[0].strip().lower() not in ('totals', 'total', 'grand total'):
                rows.append({LABELS[h]: (num(v) if LABELS[h] in METRICS else v.strip())
                             for h, v in zip(head, vals) if h in LABELS})
        found[rid] = norm_reduced(rid, {'_table': rows})
    return found

def parse_tagcov(text):
    rows = list(csv.reader([x for x in text.splitlines() if x.strip() and not x.lstrip().startswith('#')]))
    head = [h.lower() for h in rows[0]] if rows else []
    u = next((i for i, h in enumerate(head) if 'url' in h), None)
    s = next((i for i, h in enumerate(head) if 'status' in h), None)
    if u is None or s is None:
        raise ValueError('the Tag coverage file needs one column whose header contains URL and one whose header contains status')
    return {'pages': [(r[u], r[s]) for r in rows[1:] if len(r) > max(u, s)]}
