#!/usr/bin/env python3
"""Prepare verified Laravel social login in a new local candidate; never deploy or run migrations."""
import argparse
import hashlib
import json
from pathlib import Path
import shutil
import subprocess
import tarfile
import tempfile

ROOT = Path(__file__).resolve().parents[1]
BUNDLE = ROOT / 'integrations/laravel/login'


def prepare(source, snapshot, output, support_snapshot=None):
    source, output = source.resolve(), output.resolve()
    if output.exists() or source == output or source in output.parents:
        raise ValueError('Output must be a new isolated directory outside the source checkout')
    files = json.loads(snapshot.read_text())['php_files']
    manifest = json.loads((BUNDLE / 'source-manifest.json').read_text())
    support_snapshot = support_snapshot or ROOT / '.deploy/audit/php'
    for name in manifest:
        if name not in files:
            files[name] = (support_snapshot / name).read_text()
    for name, hashes in manifest.items():
        if hashlib.sha256(files[name].encode()).hexdigest() != hashes['baseline_sha256']:
            raise ValueError(f'Inspected baseline differs: {name}; reconcile it before preparing')
    output.mkdir(parents=True, mode=0o700)
    with tempfile.TemporaryFile() as archive:
        subprocess.run(['git', '-C', str(source), 'archive', '--format=tar', 'HEAD'], stdout=archive, check=True)
        archive.seek(0)
        with tarfile.open(fileobj=archive) as stream:
            stream.extractall(output, filter='data')
    for name in manifest:
        destination = output / name
        destination.parent.mkdir(parents=True, exist_ok=True)
        destination.write_text(files[name])
    patch = BUNDLE / 'verified-login.patch'
    subprocess.run(['git', 'apply', '--unidiff-zero', '--check', str(patch)], cwd=output, check=True)
    subprocess.run(['git', 'apply', '--unidiff-zero', str(patch)], cwd=output, check=True)
    for name, hashes in manifest.items():
        if hashlib.sha256((output / name).read_bytes()).hexdigest() != hashes['candidate_sha256']:
            raise ValueError(f'Candidate differs: {name}')
    for folder in ['app/Services', 'config', 'database/migrations', 'tests/Feature']:
        for file in (BUNDLE / folder).glob('*.php'):
            destination = output / folder / file.name
            destination.parent.mkdir(parents=True, exist_ok=True)
            shutil.copyfile(file, destination)
    # Retain the existing shared services required by the inspected controller.
    for file in (ROOT / 'integrations/laravel/app/Services').glob('*.php'):
        destination = output / 'app/Services' / file.name
        if not destination.exists():
            shutil.copyfile(file, destination)
    print(f'Local candidate prepared: {output}; no production files or databases changed')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--source', type=Path, required=True)
    parser.add_argument('--snapshot', type=Path, default=ROOT / '.deploy/round2-inspection.json')
    parser.add_argument('--output', type=Path, required=True)
    parser.add_argument('--support-snapshot', type=Path, default=ROOT / '.deploy/audit/php')
    args = parser.parse_args()
    prepare(args.source, args.snapshot, args.output, args.support_snapshot)
