"""Measure one downloaded JSON, CSV or plain-text range file using Python 3.
Usage: python3 measure.py saved-file [facet-name ...]
Counts distinct valid prefix strings, not addresses or a union of networks.
Bare IPs become /32 or /128. Nested/overlapping CIDRs remain separate.
"""
import hashlib
import ipaddress
import json
import sys
from pathlib import Path

META_KEYS = ['syncToken', 'createDate', 'creationTime', 'last_updated_timestamp', 'changeNumber', 'etag', 'cloud', 'version', 'updated', 'lastUpdated']


def measure(raw, facets=()):
    text = raw.decode('utf-8-sig')
    prefixes = {4: set(), 6: set()}
    values = {key: set() for key in facets}
    publisher = {}

    def address(value):
        value = value.strip()
        if '%' in value:  # Zone identifiers are not public address ranges.
            return
        try:
            if '/' in value:
                # Validate, but retain publisher spelling and overlapping ranges.
                version = ipaddress.ip_network(value, strict=False).version
                prefixes[version].add(value)
            else:
                ip = ipaddress.ip_address(value)
                prefixes[ip.version].add(f'{value}/{ip.max_prefixlen}')
        except ValueError:
            pass

    def walk(node):
        if isinstance(node, str):
            address(node)
        elif isinstance(node, list):
            for item in node:
                walk(item)
        elif isinstance(node, dict):
            for key, value in node.items():
                if key in values:
                    for item in value if isinstance(value, list) else [value]:
                        if isinstance(item, str) and item.strip():
                            values[key].add(item.strip())
                walk(value)

    try:
        document = json.loads(text)
    except json.JSONDecodeError:
        shape = 'text'
        for line in text.splitlines():
            if line.strip() and not line.lstrip().startswith('#'):
                address(line.split(',', 1)[0].strip())
    else:
        shape = 'json'
        walk(document)
        if isinstance(document, dict):
            for obj in [document, document.get('result', {})]:
                if isinstance(obj, dict):
                    for key in META_KEYS:
                        if isinstance(obj.get(key), (str, int, float)):
                            publisher[key] = str(obj[key])

    return {
        'shape': shape, 'ipv4': len(prefixes[4]), 'ipv6': len(prefixes[6]),
        'bytes': len(raw), 'sha256': hashlib.sha256(raw).hexdigest(),
        'sample': sorted(prefixes[4])[:3] + sorted(prefixes[6])[:3],
        'facetCounts': {k: len(v) for k, v in values.items() if v},
        **{f'facet_{k}': sorted(v) for k, v in values.items() if v},
        'publisher': publisher,
    }


if __name__ == '__main__':
    if len(sys.argv) < 2:
        raise SystemExit(__doc__)
    print(json.dumps(measure(Path(sys.argv[1]).read_bytes(), sys.argv[2:]), indent=2))
