from pathlib import Path, PurePosixPath
import os, re, shutil, subprocess, tarfile

home = Path('/home/justrep-ssh')
web = home / 'www'
name = 'justrec-email-verification-20261008'
archive_path = home / (name + '.tar.gz')
staging = home / (name + '-stage')
backup = home / (name + '-backup')
assert web.is_dir(), 'Web root missing'
assert archive_path.is_file(), 'Deployment archive missing'
assert not staging.exists(), 'Staging directory already exists'
assert not backup.exists(), 'Backup directory already exists'
staging.mkdir(mode=0o700)
backup.mkdir(mode=0o700)

with tarfile.open(archive_path, 'r:gz') as archive:
    members = archive.getmembers()
    paths = []
    for member in members:
        relative = PurePosixPath(member.name)
        assert not relative.is_absolute() and '..' not in relative.parts, 'Unsafe archive path'
        assert member.isfile(), 'Archive must contain regular files only'
        paths.append(relative)
    assert len(paths) == 74, 'Unexpected archive file count'
    assert PurePosixPath('index.html') in paths and PurePosixPath('.htaccess') in paths, 'Frontend entry files missing'
    assert PurePosixPath('backend/index.php') in paths, 'API router missing'
    archive.extractall(staging)

php_files = [p for p in paths if p.suffix == '.php']
assert len(php_files) == 5, 'Unexpected PHP file set'
for relative in php_files:
    source = staging.joinpath(*relative.parts)
    result = subprocess.run(['php', '-l', str(source)], capture_output=True, text=True)
    assert result.returncode == 0, result.stdout + result.stderr

index = (staging / 'index.html').read_text()
assets = set(re.findall(r'(?:src|href)="(/assets/[^\"]+)"', index))
assert assets and all((staging / asset.lstrip('/')).is_file() for asset in assets), 'Index references missing assets'

files = sorted(paths, key=lambda p: (
    3 if p.as_posix() == 'index.html' else
    2 if p.parts[0] == 'backend' and p.as_posix() == 'backend/index.php' else
    1 if p.parts[0] == 'backend' or p.suffix == '.html' or p.as_posix() == '.htaccess' else 0,
    p.as_posix()))
backed_up = []
published = []
try:
    for relative in files:
        target = web.joinpath(*relative.parts)
        if target.is_file():
            backup_target = backup.joinpath(*relative.parts)
            backup_target.parent.mkdir(parents=True, exist_ok=True)
            shutil.copy2(target, backup_target)
            backed_up.append(relative)
    for relative in files:
        source = staging.joinpath(*relative.parts)
        target = web.joinpath(*relative.parts)
        target.parent.mkdir(parents=True, exist_ok=True)
        temp = target.with_name(target.name + '.justrec-next')
        shutil.copyfile(source, temp)
        temp.chmod((target.stat().st_mode & 0o777) if target.exists() else 0o644)
        os.replace(temp, target)
        published.append(relative)
    live_assets = set(re.findall(r'(?:src|href)="(/assets/[^\"]+)"', (web / 'index.html').read_text()))
    assert live_assets and all((web / asset.lstrip('/')).is_file() for asset in live_assets), 'Live index references missing assets'
except Exception:
    for relative in reversed(published):
        target = web.joinpath(*relative.parts)
        backup_target = backup.joinpath(*relative.parts)
        if backup_target.is_file():
            shutil.copy2(backup_target, target.with_name(target.name + '.rollback'))
            os.replace(target.with_name(target.name + '.rollback'), target)
        elif target.is_file():
            target.unlink()
    raise
print('Published {} files ({} PHP files); backup={}'.format(len(files), len(php_files), backup), flush=True)
