"""Demonstrate a deleted-open file in a new Linux fixture, using at most 1 MiB of file data.

No existing files are opened or removed, no network requests or configuration
changes. Only the synthetic file created by this run is unlinked.
"""
import argparse
import datetime as dt
import errno
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess
import sys
import tempfile


def run(out):
    if sys.platform != 'linux':
        raise SystemExit('Requires Linux /proc file-descriptor semantics.')
    du = shutil.which('du')
    if not du:
        raise SystemExit('Requires existing GNU du; installs nothing.')
    version = subprocess.check_output([du, '--version'], text=True).splitlines()[0]
    if 'GNU coreutils' not in version:
        raise SystemExit('This drill requires GNU du.')
    if not Path('/proc/self/fd').is_dir():
        raise SystemExit('Requires access to this process file descriptors in /proc.')
    out.mkdir(parents=True, exist_ok=True)
    if shutil.disk_usage(out).free < 64 * 1024 * 1024:
        raise SystemExit('At least 64 MiB free in the output filesystem is required.')
    root = Path(tempfile.mkdtemp(prefix='disk-unlinked-', dir=out))
    fixture = root / 'fixture'
    fixture.mkdir()
    target = fixture / 'synthetic.log'
    payload = b'x' * (1024 * 1024)

    def directory_bytes():
        r = subprocess.run([du, '-B1', '-s', str(fixture)], capture_output=True, text=True, timeout=5)
        if r.returncode:
            raise RuntimeError('du failed on the new fixture directory')
        return int(r.stdout.split()[0])

    def state(fd):
        s = os.fstat(fd)
        return dict(logical_bytes=s.st_size, allocated_bytes=s.st_blocks * 512,
                    hard_links=s.st_nlink, original_name_exists=target.exists(),
                    fixture_du_allocated_bytes=directory_bytes())

    empty_bytes = directory_bytes()
    descriptor_path = None
    with target.open('xb+', buffering=0) as f:
        if f.write(payload) != len(payload):
            raise RuntimeError('Incomplete synthetic write')
        os.fsync(f.fileno())
        fd = f.fileno()
        descriptor_path = Path('/proc/self/fd') / str(fd)
        linked = state(fd)
        # Remove only this run's synthetic pathname, while its own descriptor is open.
        target.unlink()
        unlinked = state(fd)
        f.seek(0)
        still_readable = f.read(32) == payload[:32]
        descriptor_marked_deleted = os.readlink(descriptor_path).endswith(' (deleted)')
    closed = dict(proc_descriptor_entry_present=os.path.lexists(descriptor_path),
                  fixture_du_allocated_bytes=directory_bytes())
    try:
        os.fstat(fd)
        closed['original_descriptor_is_closed'] = False
    except OSError as ex:
        closed['original_descriptor_is_closed'] = ex.errno == errno.EBADF
    checks = dict(
        linked_name_and_one_link=linked['original_name_exists'] and linked['hard_links'] == 1,
        unlinked_name_and_zero_links=not unlinked['original_name_exists'] and unlinked['hard_links'] == 0,
        file_size_retained=linked['logical_bytes'] == unlinked['logical_bytes'] == len(payload),
        allocation_retained=linked['allocated_bytes'] == unlinked['allocated_bytes'] and unlinked['allocated_bytes'] > 0,
        du_stops_counting_unlinked_file=unlinked['fixture_du_allocated_bytes'] == empty_bytes < linked['fixture_du_allocated_bytes'],
        descriptor_readable_after_unlink=still_readable,
        proc_marks_deleted=descriptor_marked_deleted,
        descriptor_absent_after_close=closed['original_descriptor_is_closed'] and not closed['proc_descriptor_entry_present'])
    result = dict(completed_at_utc=dt.datetime.now(dt.timezone.utc).isoformat(),
                  environment=dict(os=platform.system(),kernel=platform.release(),python=platform.python_version(),du=version),
                  synthetic_file_bytes=len(payload),fixture_before= dict(du_allocated_bytes=empty_bytes),
                  linked=linked,unlinked_while_open=unlinked,after_close=closed,checks=checks,passed=all(checks.values()),
                  scope='One newly created 1 MiB synthetic file on a local Linux filesystem. No real logs, daemon restart, disk-full or inode-exhaustion test. Filesystem-wide free-space changes are not measured. Closing the descriptor is checked directly; block reclamation timing is not inferred from df.')
    (root/'result.json').write_text(json.dumps(result,indent=2)+'\n',encoding='utf-8')
    print(json.dumps(dict(result_file=str(root/'result.json'),passed=result['passed'])))
    return 0 if result['passed'] else 1


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--out',type=Path,required=True)
    raise SystemExit(run(parser.parse_args().out))
