#!/usr/bin/env python3
"""Fail-closed feature/license/dependency audit for Setrika's macOS FFmpeg CLI pair."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import platform
import re
import shlex
import subprocess

FORBIDDEN = {'gpl', 'nonfree', 'version3', 'libx264', 'libx265', 'libvmaf', 'libfdk-aac', 'libvidstab', 'librubberband', 'libxvid', 'openssl', 'gnutls', 'mbedtls'}
REQUIRED = {
    'protocols': {'file', 'pipe', 'tcp', 'udp', 'rtp'},
    'demuxers': {'mov', 'rtsp', 'h264', 'image2', 'image2pipe', 'jpeg_pipe', 'png_pipe'},
    'muxers': {'mp4', 'segment', 'image2pipe'},
    'decoders': {'h264', 'mjpeg', 'png'},
    'encoders': {'mjpeg', 'png'},
    'filters': {'select', 'scale', 'format'},
    'bsfs': {'h264_mp4toannexb', 'extract_extradata'},
}


def configuration_flags(text):
    lines = []
    for line in text.splitlines():
        if line.strip().startswith('--'):
            lines.extend(shlex.split(line.strip()))
        elif 'configuration:' in line:
            lines.extend(shlex.split(line.split('configuration:', 1)[1]))
    return lines


def capability_names(kind, text):
    result = set()
    for line in text.splitlines():
        if kind in {'protocols', 'bsfs'}:
            match = re.fullmatch(r'\s*([a-z0-9_]+)\s*', line)
        else:
            match = re.match(r'^\s*[A-Z.]{1,6}\s+([a-z0-9_,]+)\s', line)
        if match:
            result.update(match.group(1).split(','))
    return result


def check_outputs(license_text, buildconf, features, dependencies):
    errors = []
    license_text = ' '.join(license_text.split())
    if 'GNU Lesser General Public License' not in license_text or 'version 2.1' not in license_text:
        errors.append('Expected FFmpeg LGPL version 2.1-or-later license')
    if 'GNU General Public License' in license_text or 'nonfree' in license_text.lower():
        errors.append('GPL/nonfree license output is forbidden')
    flags = configuration_flags(buildconf)
    for flag in flags:
        if flag.startswith('--enable-') and (flag[9:].split('=',1)[0] in FORBIDDEN or flag.startswith('--enable-lib')):
            errors.append(f'Forbidden configure flag: {flag}')
    if '--disable-autodetect' not in flags:
        errors.append('External library autodetection must be disabled')
    if '--disable-shared' not in flags or '--enable-static' not in flags:
        errors.append('This qualified CLI recipe requires its FFmpeg libraries statically embedded')
    for kind, needed in REQUIRED.items():
        missing = needed - capability_names(kind, features.get(kind, ''))
        if missing: errors.append(f'Missing {kind}: {", ".join(sorted(missing))}')
    unexpected_encoders = capability_names('encoders', features.get('encoders', '')) - {'mjpeg', 'png', 'wrapped_avframe'}
    if unexpected_encoders: errors.append('Unexpected encoders: ' + ', '.join(sorted(unexpected_encoders)))
    if not dependencies:
        errors.append('Native dependencies could not be inspected')
    for dependency in dependencies:
        if not dependency.startswith(('/usr/lib/', '/System/Library/')):
            errors.append(f'Non-system dependency forbidden in portable macOS CLI: {dependency}')
        if any(name in dependency.lower() for name in ('x264', 'x265', 'vmaf')):
            errors.append(f'Forbidden external dependency: {dependency}')
    return errors


def output(command):
    result = subprocess.run(command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=30, check=True)
    return result.stdout


def native_dependencies(binary):
    if platform.system() != 'Darwin':
        raise RuntimeError('Native dependency audit qualified only on macOS; Windows PE audit remains required')
    listing = output(['/usr/bin/otool', '-L', str(binary)])
    dependencies = [line.strip().split(' (',1)[0] for line in listing.splitlines()[1:] if line.strip()]
    architecture = output(['/usr/bin/lipo', '-archs', str(binary)]).strip()
    return dependencies, architecture


def audit(ffmpeg, ffprobe):
    records, errors = {}, []
    for name, binary in [('ffmpeg',ffmpeg),('ffprobe',ffprobe)]:
        license_text=output([str(binary),'-hide_banner','-L'])
        buildconf=output([str(binary),'-hide_banner','-buildconf'])
        features={kind:output([str(binary),'-hide_banner','-'+kind]) for kind in REQUIRED}
        dependencies, architecture=native_dependencies(binary)
        failures=check_outputs(license_text,buildconf,features,dependencies)
        if architecture != 'arm64':failures.append(f'Expected arm64, got {architecture}')
        errors.extend(f'{name}: {failure}' for failure in failures)
        records[name]={'sha256':hashlib.sha256(binary.read_bytes()).hexdigest(),'bytes':binary.stat().st_size,
                       'version':output([str(binary),'-version']).splitlines()[0], 'architecture':architecture,
                       'license':license_text,'buildconf':buildconf,'dependencies':dependencies,
                       'features':{kind:sorted(capability_names(kind,text)) for kind,text in features.items()}}
    return {'schema_version':1,'passed':not errors,'errors':errors,'tools':records,
            'scope':'Build flags, reported LGPL2.1 license, feature availability and macOS dynamic dependencies; not a complete legal/patent/distribution opinion.'}


if __name__ == '__main__':
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--ffmpeg',type=Path,required=True);parser.add_argument('--ffprobe',type=Path,required=True)
    parser.add_argument('--output',type=Path)
    args=parser.parse_args()
    report=audit(args.ffmpeg.resolve(),args.ffprobe.resolve())
    if args.output:
        args.output.parent.mkdir(parents=True,exist_ok=True);args.output.write_text(json.dumps(report,ensure_ascii=False,indent=2)+'\n')
    print(json.dumps({'passed':report['passed'],'errors':report['errors']},ensure_ascii=False,indent=2))
    raise SystemExit(0 if report['passed'] else 1)
