"""Read exact requested network ownership without admitting any address-space allocation."""
import re

from network_inventory import Reader
from network_pools import InventoryFailure, decode, prefix

LISTING = ['docker', 'network', 'ls', '--no-trunc', '--format',
           '{"Id":{{json .ID}},"Name":{{json .Name}}}']


def listing(raw, names):
    rows, identifiers, selected = raw.splitlines(), set(), {}
    if len(rows) > 1024:
        raise InventoryFailure('inventory_invalid')
    for raw_row in rows:
        row = decode(raw_row)
        if (not isinstance(row, dict) or set(row) != {'Id', 'Name'}
                or not isinstance(row['Id'], str) or not re.fullmatch('[a-f0-9]{64}', row['Id'])
                or not isinstance(row['Name'], str)
                or not re.fullmatch('[A-Za-z0-9][A-Za-z0-9_.-]{0,255}', row['Name'])
                or row['Id'] in identifiers):
            raise InventoryFailure('inventory_invalid')
        identifiers.add(row['Id'])
        if row['Name'] in names:
            if row['Name'] in selected:
                raise InventoryFailure('inventory_invalid')
            selected[row['Name']] = row['Id']
    return selected


def string_mapping(value):
    if value is None:
        return {}
    if (not isinstance(value, dict)
            or any(not isinstance(k, str) or not isinstance(v, str) for k, v in value.items())):
        raise InventoryFailure('inventory_invalid')
    return dict(value)


def ownership_ipam(ipam):
    """Validate subnet evidence without assuming the selected network's IPAM driver."""
    if (not isinstance(ipam, dict) or set(ipam) - {'Driver', 'Options', 'Config'}
            or not isinstance(ipam.get('Driver'), str) or not 1 <= len(ipam['Driver']) <= 256
            or 'Config' not in ipam):
        raise InventoryFailure('inventory_invalid')
    pools = ipam['Config']
    if pools is None:
        pools = []
    if not isinstance(pools, list) or len(pools) > 64:
        raise InventoryFailure('inventory_invalid')
    subnets = []
    for pool in pools:
        if not isinstance(pool, dict) or set(pool) - {'Subnet', 'Gateway', 'IPRange', 'AuxiliaryAddresses'}:
            raise InventoryFailure('inventory_invalid')
        subnet = prefix(pool.get('Subnet'), strict=True)
        for key in ('Gateway', 'IPRange'):
            if pool.get(key):
                address = prefix(pool[key])
                if address.version != subnet.version or not address.subnet_of(subnet):
                    raise InventoryFailure('inventory_invalid')
        for value in string_mapping(pool.get('AuxiliaryAddresses')).values():
            address = prefix(value)
            if address.version != subnet.version or not address.subnet_of(subnet):
                raise InventoryFailure('inventory_invalid')
        subnets.append(dict(pool))
    return {'Driver': ipam['Driver'], 'Options': string_mapping(ipam.get('Options')), 'Config': subnets}


def ownership_record(row):
    """Keep the private contract fields; public receipts must project this record separately."""
    if not isinstance(row, dict):
        raise InventoryFailure('inventory_invalid')
    result = {key: row.get(key) for key in ('Id', 'Name', 'Driver', 'Scope', 'Internal', 'Attachable', 'EnableIPv6')}
    if (not isinstance(result['Driver'], str) or not 1 <= len(result['Driver']) <= 256
            or result['Scope'] not in ('local', 'swarm', 'global')
            or any(type(result[key]) is not bool for key in ('Internal', 'Attachable', 'EnableIPv6'))):
        raise InventoryFailure('inventory_invalid')
    result.update(Labels=string_mapping(row.get('Labels')), Options=string_mapping(row.get('Options')),
                  IPAM=ownership_ipam(row.get('IPAM')))
    return result


def inspect_selected(read, selected):
    networks = []
    identities = sorted(selected.values())
    for offset in range(0, len(identities), 32):
        batch = identities[offset:offset + 32]
        rows = read(['docker', 'network', 'inspect', *batch])
        if not isinstance(rows, list) or len(rows) != len(batch):
            raise InventoryFailure('inventory_invalid')
        observed = set()
        for row in rows:
            network = ownership_record(row)
            name, identity = network['Name'], network['Id']
            if (not isinstance(name, str) or not isinstance(identity, str)
                    or selected.get(name) != identity or identity not in batch or identity in observed):
                raise InventoryFailure('network_inventory_changed')
            observed.add(identity)
            networks.append(network)
    return networks


def ownership_inventory(run, plans):
    """Inspect complete exact-name matches and recheck them; unrelated details stay unread."""
    if not isinstance(plans, list) or len(plans) > 64:
        raise InventoryFailure('inventory_invalid')
    names = set()
    for plan in plans:
        if (not isinstance(plan, dict) or not isinstance(plan.get('name'), str)
                or not re.fullmatch('[A-Za-z0-9][A-Za-z0-9_.-]{0,255}', plan['name'])):
            raise InventoryFailure('inventory_invalid')
        names.add(plan['name'])
    read = Reader(run)
    selected = listing(read(LISTING, parsed=False), names)
    networks = inspect_selected(read, selected)
    if listing(read(LISTING, parsed=False), names) != selected:
        raise InventoryFailure('network_inventory_changed')
    return {'networks': networks}
