"""Copy approved temporary files without following links or restoring runtime state."""
import collections
import datetime
import fnmatch
import hashlib
import json
import os
from pathlib import Path
import shutil
import stat
import subprocess

ROOT = Path(__file__).resolve().parents[1]
REPORT = ROOT / 'reports'
os.umask(0o077)
assert os.geteuid() == 0
assert ROOT.is_relative_to('/data/codex-artifacts/host-maintenance')
assert shutil.disk_usage(ROOT).free > 40 * 1024**3
DEST = ROOT / 'files/tmp'
DEST.mkdir(parents=True, exist_ok=True)
SENSITIVE = {'.env', 'auth.json', 'credentials.json', 'password', 'password.txt',
             'sudo_password', 'token', 'token.txt', 'session.json', 'login-body.json',
             'id_rsa', 'id_ed25519', '.git-credentials', '.netrc'}
PATTERNS = ('*.credentials.json', '*-credentials.json', '*_credentials.json',
            '*.key', '*.p12', '*.pfx', '.env.*')
PRIVATE_DIRS = {'.ssh', 'secrets', 'credentials', 'credstore', 'credstore.encrypted'}
mounts = json.loads(subprocess.check_output(
    ['findmnt', '--json', '--list', '--output', 'TARGET'], text=True))
mountpoints = {row['target'] for row in mounts.get('filesystems', [])}


def sensitive(path, is_dir):
    name = path.name.lower()
    if name in SENSITIVE or any(fnmatch.fnmatch(name, p) for p in PATTERNS):
        return True
    return is_dir and name in PRIVATE_DIRS


def inventory(label):
    rows, omitted = [], []
    device = os.lstat('/tmp').st_dev
    stack = [Path('/tmp')]
    while stack:
        parent = stack.pop()
        with os.scandir(parent) as entries:
            for entry in entries:
                path = Path(entry.path)
                relative = str(path.relative_to('/tmp'))
                try:
                    st = entry.stat(follow_symlinks=False)
                except FileNotFoundError:
                    omitted.append({'path': relative, 'reason': 'vanished'})
                    continue
                is_dir = stat.S_ISDIR(st.st_mode)
                reason = None
                if str(path) in mountpoints or st.st_dev != device:
                    reason = 'nested_mount'
                elif parent == Path('/tmp') and (entry.name.startswith('systemd-private-')
                                                or entry.name == 'snap-private-tmp'):
                    reason = 'service_private_runtime'
                elif sensitive(path, is_dir):
                    reason = 'known_credential_name'
                elif not (is_dir or stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
                    reason = 'socket_fifo_or_device'
                if reason:
                    omitted.append({'path': relative, 'reason': reason})
                    continue
                kind = 'directory' if is_dir else 'symlink' if stat.S_ISLNK(st.st_mode) else 'file'
                row = {'path': relative, 'kind': kind, 'size': st.st_size,
                       'mtime_ns': st.st_mtime_ns, 'uid': st.st_uid, 'gid': st.st_gid,
                       'mode': stat.S_IMODE(st.st_mode)}
                if kind == 'symlink':
                    row['target'] = os.readlink(path)
                rows.append(row)
                if is_dir:
                    stack.append(path)
    rows.sort(key=lambda r: r['path'])
    manifest = REPORT / f'tmp-{label}.json'
    manifest.write_text(json.dumps({'files': rows, 'excluded': omitted}, ensure_ascii=True) + '\n')
    filelist = REPORT / f'tmp-{label}.files0'
    filelist.write_bytes(b''.join(os.fsencode(r['path']) + b'\0' for r in rows))
    return rows, omitted, filelist


def copy_pass(label, filelist, verify=False):
    command = ['rsync', '-aHAX', '--no-recursive', '--dirs', '--no-devices', '--no-specials',
               '--numeric-ids', '--one-file-system', '--from0', '--protect-args',
               '--timeout=60', '--bwlimit=51200', f'--files-from={filelist}']
    if verify:
        command += ['--checksum', '--dry-run', '--itemize-changes', '--out-format=%i %n%L']
    else:
        command += ['--stats']
    command += ['/tmp/', str(DEST) + '/']
    with (REPORT / f'rsync-{label}.log').open('w') as log:
        result = subprocess.run(command, stdout=log, stderr=subprocess.STDOUT, timeout=900)
    assert result.returncode in (0, 24), f'rsync failed ({result.returncode}); inspect protected log'
    return result.returncode


passes = []
for label in ('initial', 'final'):
    rows, omitted, filelist = inventory(label)
    print(json.dumps({'phase': label, 'entries': len(rows),
                      'bytes': sum(r['size'] for r in rows if r['kind'] == 'file'),
                      'excluded': len(omitted)}), flush=True)
    passes.append({'phase': label, 'returncode': copy_pass(label, filelist)})

code = copy_pass('verify', filelist, verify=True)
checks = []
for row in rows:
    if row['kind'] != 'file' or row['size'] > 4 * 1024**2:
        continue
    if Path(row['path']).name not in ('checkpoint.ko.md', 'checkpoint.json', 'manifest.json', 'batch.json'):
        continue
    target = DEST / row['path']
    if target.is_file() and not target.is_symlink():
        checks.append({'path': row['path'], 'sha256': hashlib.sha256(target.read_bytes()).hexdigest()})
report = {'completed_at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
          'source': '/tmp', 'destination': str(DEST), 'passes': passes,
          'checksum_dry_run_returncode': code,
          'counts': dict(collections.Counter(r['kind'] for r in rows)),
          'regular_bytes': sum(r['size'] for r in rows if r['kind'] == 'file'),
          'excluded_counts': dict(collections.Counter(r['reason'] for r in omitted)),
          'critical_backup_hashes': checks,
          'limitations': ['live file copy, not atomic filesystem snapshot',
                          'known credential names excluded; archive may still contain sensitive data',
                          'root-only local backup; no automatic restore; no deletion of source']}
(ROOT / 'manifest.json').write_text(json.dumps(report, indent=2, ensure_ascii=True) + '\n')
print(json.dumps({k: v for k, v in report.items() if k != 'critical_backup_hashes'}), flush=True)
