"""Build a source-only staging archive; never copy local credentials or data."""
import hashlib
import argparse
import io
import json
from pathlib import Path
import tarfile

ROOT = Path(__file__).resolve().parents[2]


def build(output_name='ftg2-staging-20261106'):
    if not output_name or any(c not in 'abcdefghijklmnopqrstuvwxyz0123456789-' for c in output_name):
        raise ValueError('Bundle name must contain lowercase letters, digits and hyphens only.')
    roots = [line[1:-1] for line in (ROOT / '.dockerignore').read_text().splitlines()
             if line.startswith('!') and line.endswith('/') and line.count('/') == 1]
    paths = {ROOT / name for name in ('manage.py', 'requirements.txt', '.dockerignore')}
    for name in roots:
        if name == 'frontend':
            continue
        for path in (ROOT / name).rglob('*.py'):
            relative = path.relative_to(ROOT)
            if ('tests' in relative.parts or '__pycache__' in relative.parts
                    or path.name in {'testing.py', 'e2e_settings.py', 'rehearsal_settings.py',
                                     'rehearsal_tasks.py', 'prepare_browser_tests.py'}
                    or path.name.endswith('test_settings.py')):
                continue
            paths.add(path)
    paths.add(ROOT / 'federations/country_catalog.json')
    paths.update(path for path in (ROOT / 'frontend/src').rglob('*') if path.is_file())
    for pattern in ('package*.json', 'tsconfig*.json', 'index.html', 'vite.config.ts'):
        paths.update((ROOT / 'frontend').glob(pattern))
    paths.update(path for path in (ROOT / 'deploy/beta').rglob('*') if path.is_file()
                 and '__pycache__' not in path.parts
                 and (not path.name.startswith('.env') or path.name == '.env.server.example'))
    paths.update(ROOT / name for name in ('docs/SERVER_READINESS_FA_20261105.md',
                                         'docs/JOURNEY_FIXES_AND_STAGING_FA_20261106.md'))
    payloads = {}
    for path in sorted(paths):
        if path.is_symlink() or not path.is_file():
            raise ValueError(f'Missing or symbolic source file: {path.relative_to(ROOT)}')
        relative = path.resolve().relative_to(ROOT).as_posix()
        lower = path.name.lower()
        if (lower.endswith(('.dump', '.sqlite3', '.pem', '.key', '.bak', '.sql'))
                or 'credentials' in lower or ('/.env' in '/' + relative and lower != '.env.server.example')):
            raise ValueError(f'Forbidden source file: {relative}')
        payloads[relative] = path.read_bytes()
    manifest = {name: {'sha256': hashlib.sha256(data).hexdigest(), 'bytes': len(data)}
                for name, data in payloads.items()}
    payloads['STAGING_MANIFEST.json'] = json.dumps(manifest, indent=2).encode()
    output = ROOT / f'var/staging/{output_name}.tar.gz'
    output.parent.mkdir(parents=True, exist_ok=True)
    with tarfile.open(output, 'w:gz') as archive:
        for name, data in payloads.items():
            info = tarfile.TarInfo(name)
            info.size = len(data)
            info.mode = 0o755 if name.endswith('.sh') else 0o644
            archive.addfile(info, io.BytesIO(data))
    # Verify archive contents against source hashes before handing it over.
    with tarfile.open(output, 'r:gz') as archive:
        for name, row in manifest.items():
            assert hashlib.sha256(archive.extractfile(name).read()).hexdigest() == row['sha256']
    checksum = hashlib.sha256(output.read_bytes()).hexdigest()
    output.with_suffix(output.suffix + '.sha256').write_text(f'{checksum}  {output.name}\n')
    print(json.dumps({'path': str(output), 'files': len(manifest), 'sha256': checksum}))


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--name', default='ftg2-staging-20261106')
    build(parser.parse_args().name)
