#!/usr/bin/env python3
"""Read-only Raven Aerial inventory. Never moves, modifies, or deletes media."""
import argparse
import hashlib
import json
import os
from datetime import datetime, timezone
from pathlib import Path

EXTENSIONS = {'.jpg', '.jpeg', '.png', '.webp', '.tif', '.tiff', '.heic', '.avif', '.dng', '.cr2', '.cr3', '.nef', '.arw', '.orf', '.raf', '.rw2', '.mp4', '.mov', '.mkv'}

def scan(root, previous=None):
    prior = {p['id']: p for p in (previous or {}).get('photos', [])}
    photos, errors = [], []
    def walk_error(error):
        errors.append({'path': str(error.filename), 'error': str(error)})
    for folder, dirs, names in os.walk(root, followlinks=False, onerror=walk_error):
        dirs.sort()
        for name in sorted(names):
            path = Path(folder) / name
            if path.is_symlink() or path.suffix.lower() not in EXTENSIONS:
                continue
            relative = path.relative_to(root).as_posix()
            try:
                before = path.stat()
                digest = hashlib.sha256()
                with path.open('rb') as stream:
                    for chunk in iter(lambda: stream.read(1024 * 1024), b''):
                        digest.update(chunk)
                after = path.stat()
                if (before.st_size, before.st_mtime_ns) != (after.st_size, after.st_mtime_ns):
                    raise ValueError('File changed during scan; scan again after transfer finishes')
                ident = hashlib.sha256(relative.encode()).hexdigest()
                old = prior.get(ident, {})
                unchanged = old.get('sha256') == digest.hexdigest()
                photos.append({'id': ident, 'path': relative, 'bytes': after.st_size,
                    'modifiedAt': datetime.fromtimestamp(after.st_mtime, timezone.utc).isoformat(),
                    'sha256': digest.hexdigest(),
                    'collection': old.get('collection', '') if unchanged else '',
                    'status': old.get('status', 'review') if unchanged else 'review'})
            except (OSError, ValueError) as error:
                errors.append({'path': relative, 'error': str(error)})
    return {'version': 1, 'generatedAt': datetime.now(timezone.utc).isoformat(), 'photos': photos, 'errors': errors}

def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('root', type=Path)
    parser.add_argument('--output', type=Path, required=True)
    parser.add_argument('--previous', type=Path, help='Reviewed inventory to preserve decisions for unchanged files')
    args = parser.parse_args()
    root = args.root.resolve()
    if not root.is_dir():
        parser.error('Photo root must be an existing directory')
    previous = json.loads(args.previous.read_text()) if args.previous else None
    result = scan(root, previous)
    # Refuse to overwrite an existing inventory or media file.
    with args.output.open('x', encoding='utf-8') as stream:
        json.dump(result, stream, indent=2)
    print(f"Inventoried {len(result['photos'])} files. {len(result['errors'])} scan errors. No media changed.")

if __name__ == '__main__':
    main()
