import hashlib
import json
import os
from pathlib import Path
import subprocess
import sys
import time

sys.path.insert(0, '/usr/local/lib/contextra-maintenance/20260928-r01')
import field_profiles as profiles
import field_network as network
import yaml

p = profiles.load('field')
assert p['network'] == dict(interface='enp128s31f6', mode='static', address='192.168.105.40/24', gateway='', dns=[], ssh_port=1023)
assert profiles.load()['id'] == 'field'
assert not (profiles.STATE / 'pending.json').exists()
assert not (profiles.STATE / 'network-pending.json').exists()
assert network.addresses('enp128s31f6') == ['192.168.50.7']
assert __import__('shutil').disk_usage('/').free > 15 * 1024**3
active = Path('/workspace/contextra/runtime/state/active.json')
active_before = active.read_bytes()
changes = {}
docs = {}
for path in sorted(Path('/etc/netplan').glob('*.yaml')):
    profiles.safe_path(path)
    raw = path.read_bytes()
    doc = yaml.safe_load(raw)
    assert isinstance(doc, dict)
    docs[path.name] = doc
    interfaces = doc.get('network', {}).get('ethernets', {})
    if 'enp128s31f6' in interfaces:
        del interfaces['enp128s31f6']
        changes[path] = (raw, (json.dumps(doc, indent=2) + '\n').encode())
target = Path('/etc/netplan/90-contextra.yaml')
doc = docs.get(target.name, {'network': {'version': 2, 'renderer': 'networkd'}})
doc['network'].setdefault('ethernets', {}).update(network.render(p)['network']['ethernets'])
changes[target] = (target.read_bytes() if target.exists() else None, (json.dumps(doc, indent=2) + '\n').encode())
cloud = Path('/etc/cloud/cloud.cfg.d/99-contextra-disable-network-config.cfg')
assert not cloud.exists()
changes[cloud] = (None, b'network: {config: disabled}\n')

backup = profiles.STATE / ('nextboot-field-' + time.strftime('%Y%m%dT%H%M%SZ', time.gmtime()))
backup.mkdir(mode=0o700)
validation = backup / 'validation-root'
(validation / 'etc/netplan').mkdir(parents=True, mode=0o700)
for name, doc in docs.items():
    profiles.atomic(validation / 'etc/netplan' / name, (json.dumps(doc, indent=2) + '\n').encode())
profiles.atomic(validation / 'etc/netplan' / target.name, changes[target][1])
subprocess.run(['netplan', 'generate', '--root-dir', str(validation)], check=True, capture_output=True, timeout=30)
generated = list((validation / 'run/systemd/network').glob('*.network'))
matches = [q.read_text() for q in generated if 'Name=enp128s31f6' in q.read_text()]
assert len(matches) == 1
assert 'Address=192.168.105.40/24' in matches[0]
assert 'DHCP=ipv4' not in matches[0] and 'DHCP=yes' not in matches[0]
record = {'status': 'PREPARED_NEXT_BOOT_NOT_LIVE_APPLIED', 'profile': 'field', 'current_ip': '192.168.50.7', 'next_ip': '192.168.105.40/24', 'backup': str(backup), 'files': {str(q): {'existed': before is not None, 'before_sha256': hashlib.sha256(before).hexdigest() if before is not None else None, 'after_sha256': hashlib.sha256(after).hexdigest()} for q, (before, after) in changes.items()}}
for q, (before, _) in changes.items():
    if before is not None:
        profiles.atomic(backup / q.name, before)
profiles.atomic(backup / 'receipt.json', record)
written = []
try:
    for q, (before, after) in changes.items():
        assert (q.read_bytes() if q.exists() else None) == before
        profiles.atomic(q, after)
        written.append(q)
    for q, (_, after) in changes.items():
        assert q.read_bytes() == after
    assert active.read_bytes() == active_before
    assert network.addresses('enp128s31f6') == ['192.168.50.7']
except BaseException:
    for q in reversed(written):
        before = changes[q][0]
        if before is None:
            q.unlink()
        else:
            profiles.atomic(q, before)
    raise
os.sync()
print(json.dumps(record))
