"""Reproduce FortuneLeaf's file comparison in a new temporary directory.

No input paths, network access, installed packages or personal files are used.
Only generated sample files inside TemporaryDirectory are written and removed.
"""
import hashlib
import io
import json
from pathlib import Path
import shutil
import sys
import tempfile
import unicodedata
import zipfile


def sample_files():
    return {
        'reports/january.txt': b'amount=40\nstatus=draft\n',
        'reports-copy/january.txt': b'amount=40\nstatus=draft\n',
        'reports-final/january.txt': b'amount=41\nstatus=final\n',
        'reports/notes.txt': b'same\n',
        'archive/notes-copy.txt': b'same\n',
        'case/same-size-a.txt': b'price=40\n',
        'case/same-size-b.txt': b'price=41\n',
        'line/lf.txt': b'task=done\n',
        'line/crlf.txt': b'task=done\r\n',
        'unicode/nfc.txt': '메모=가\n'.encode('utf-8'),
        'unicode/nfd.txt': unicodedata.normalize('NFD', '메모=가\n').encode('utf-8'),
        'zero/empty.txt': b'',
    }


def sample_zip():
    stream = io.BytesIO()
    with zipfile.ZipFile(stream, 'w', compression=zipfile.ZIP_DEFLATED) as archive:
        for name, data in sample_files().items():
            info = zipfile.ZipInfo(name, date_time=(2026, 10, 24, 0, 0, 0))
            info.compress_type = zipfile.ZIP_DEFLATED
            archive.writestr(info, data)
    return stream.getvalue()


def inspect(folder):
    return {
        str(file.relative_to(folder)).replace('\\', '/'): {
            'bytes': file.stat().st_size,
            'sha256': hashlib.sha256(file.read_bytes()).hexdigest(),
        }
        for file in sorted(folder.rglob('*')) if file.is_file()
    }


def compare(expected, observed):
    return {
        'missing': sorted(set(expected) - set(observed)),
        'unexpected': sorted(set(observed) - set(expected)),
        'content_mismatch': sorted(name for name in set(expected) & set(observed)
                                   if expected[name] != observed[name]),
        'matching': sum(expected[name] == observed[name]
                        for name in set(expected) & set(observed)),
    }


def run():
    with tempfile.TemporaryDirectory(prefix='fortuneleaf-file-check-') as temporary:
        root = Path(temporary)
        original = root / 'original'
        for name, data in sample_files().items():
            file = original / name
            file.parent.mkdir(parents=True, exist_ok=True)
            file.write_bytes(data)
        manifest = inspect(original)
        digest_groups = {}
        for name, value in manifest.items():
            digest_groups.setdefault(value['sha256'], []).append(name)
        pairs = [
            ('same_name_same_bytes', 'reports/january.txt', 'reports-copy/january.txt'),
            ('same_name_different_bytes', 'reports/january.txt', 'reports-final/january.txt'),
            ('different_name_same_bytes', 'reports/notes.txt', 'archive/notes-copy.txt'),
            ('same_size_different_bytes', 'case/same-size-a.txt', 'case/same-size-b.txt'),
            ('line_endings', 'line/lf.txt', 'line/crlf.txt'),
            ('unicode_normalization', 'unicode/nfc.txt', 'unicode/nfd.txt'),
        ]
        comparisons = []
        for name, left, right in pairs:
            a, b = (original / left).read_bytes(), (original / right).read_bytes()
            comparisons.append({
                'case': name, 'left': left, 'right': right,
                'same_basename': Path(left).name == Path(right).name,
                'left_bytes': len(a), 'right_bytes': len(b),
                'same_sha256': manifest[left]['sha256'] == manifest[right]['sha256'],
                'bytes_equal': a == b,
            })
        restored = root / 'restored'
        with zipfile.ZipFile(io.BytesIO(sample_zip())) as archive:
            # Every member is defined above; this is not an arbitrary ZIP extractor.
            archive.extractall(restored)
        good = compare(manifest, inspect(restored))
        direct_byte_matches = sum((original / name).read_bytes() == (restored / name).read_bytes()
                                  for name in manifest)
        damaged = root / 'damaged'
        shutil.copytree(restored, damaged)
        (damaged / 'zero/empty.txt').unlink()
        (damaged / 'reports-final/january.txt').write_bytes(b'amount=00\nstatus=final\n')
        bad = compare(manifest, inspect(damaged))
        assert len(manifest) == 12 and len(digest_groups) == 10
        assert [x['bytes_equal'] for x in comparisons] == [True, False, True, False, False, False]
        assert all(x['same_sha256'] == x['bytes_equal'] for x in comparisons)
        assert good == {'missing': [], 'unexpected': [], 'content_mismatch': [], 'matching': 12}
        assert direct_byte_matches == 12
        assert bad == {'missing': ['zero/empty.txt'], 'unexpected': [],
                       'content_mismatch': ['reports-final/january.txt'], 'matching': 10}
        return {
            'date': '2026-10-24',
            'method': 'Generated UTF-8 and byte samples; SHA-256 and direct byte comparison; ZIP round trip; deliberate missing and changed sample.',
            'scope': '12 generated regular files only. No personal files, permissions, timestamps, symlinks, cloud history or disaster recovery were tested.',
            'python': '.'.join(map(str, sys.version_info[:3])),
            'files': manifest, 'file_count': 12, 'distinct_byte_contents': len(digest_groups),
            'duplicate_byte_groups': sorted(sorted(names) for names in digest_groups.values() if len(names) > 1),
            'comparisons': comparisons,
            'zip_restore': {**good, 'direct_byte_matches': direct_byte_matches},
            'deliberately_damaged_restore': bad,
        }


if __name__ == '__main__':
    print(json.dumps(run(), ensure_ascii=False, indent=2))
