"""Activate a verified JS-only mobile fix; reuse all data, wasm and server config."""
import datetime
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import sys
import tarfile
import urllib.error
import urllib.request


def sha(path):
    with path.open('rb') as stream:
        return hashlib.file_digest(stream, 'sha256').hexdigest()


def request(path):
    req = urllib.request.Request('https://ro-th.online' + path,
                                 headers={'Accept-Encoding': 'identity', 'Cache-Control': 'no-cache'})
    try:
        return urllib.request.urlopen(req, timeout=45)
    except urllib.error.HTTPError as error:
        return error


def check_https(path, expected):
    with request(path) as response:
        assert response.status == 200, (path, response.status)
        assert hashlib.sha256(response.read()).hexdigest() == expected, path


task, archive_hash = sys.argv[1:]
assert re.fullmatch(r'[a-z0-9-]+', task)
assert re.fullmatch(r'[a-f0-9]{64}', archive_hash)
root = Path('/var/www/html/ro-th-online')
archive = root / 'staging' / (task + '.tar.gz')
assert not archive.is_symlink() and sha(archive) == archive_hash
stage = root / 'staging' / task
backup = root / 'deploy' / task
index = root / 'mobile/index.html'
assert not stage.exists() and not backup.exists(), 'Task already staged or activated'
stage.mkdir(mode=0o700)
with tarfile.open(archive) as tar:
    members = tar.getmembers()
    names = [item.name for item in members]
    assert len(names) == 3 and len(set(names)) == 3
    assert 'release.json' in names and 'index.html' in names
    for item in members:
        assert item.isfile() and '/' not in item.name and '\\' not in item.name
        assert 0 < item.size < 2_000_000
        assert item.name in ('release.json', 'index.html') or re.fullmatch(r'WebPreview\.framework\.[a-f0-9]{12}\.js', item.name)
        with tar.extractfile(item) as source, (stage / item.name).open('xb') as target:
            shutil.copyfileobj(source, target)

manifest = json.loads((stage / 'release.json').read_text(encoding='utf-8-sig'))
assert manifest['task'] == task and manifest['configChanged'] is False
release = manifest['baseRelease']
assert re.fullmatch(r'mobile-[a-f0-9]{8}', release)
name = manifest['frameworkFile']
assert re.fullmatch(r'WebPreview\.framework\.[a-f0-9]{12}\.js', name)
assert set(names) == {'release.json', 'index.html', name}
public = root / 'public/Build' / release
destination = public / name
assert str(destination) == manifest['frameworkDestination'] and not destination.exists()
assert not index.is_symlink() and sha(index) == manifest['previousIndexHash'], 'Live HTML changed; rebase required'
assert sha(stage / name) == manifest['frameworkSha256']
assert sha(stage / 'index.html') == manifest['indexSha256']
old_html = index.read_text()
new_html = (stage / 'index.html').read_text()
expected_html = old_html.replace("frameworkUrl: buildUrl + '/WebPreview.framework.js'",
                                "frameworkUrl: buildUrl + '/" + name + "'")
# A JS-only fix may also update the landing page keyboard bridge. Explicit
# before/after snippets keep activation from carrying unrelated HTML changes.
for change in manifest.get('htmlChanges', []):
    assert change['before'] and expected_html.count(change['before']) == 1
    expected_html = expected_html.replace(change['before'], change['after'])
assert new_html == expected_html, 'Unexpected HTML changes'
assert new_html != old_html
assert set(manifest['baseFiles']) == {'WebPreview.wasm', 'WebPreview.loader.js', 'Tahoma.ttf'}
for filename, expected in manifest['baseFiles'].items():
    assert sha(public / filename) == expected, filename
    check_https('/Mobile/Build/' + release + '/' + filename, expected)
for url in ['/Mobile/', '/Mobile/?installed=1']:
    check_https(url, manifest['previousIndexHash'])

backup.mkdir(mode=0o700)
shutil.copy2(index, backup / 'index-before.html')
pending = stage / name
pending.chmod(0o644)
pending.rename(destination)
check_https('/Mobile/Build/' + release + '/' + name, manifest['frameworkSha256'])
assert sha(index) == manifest['previousIndexHash'], 'Live HTML changed during upload'
info = index.stat()
pending = stage / 'index.html'
os.chmod(pending, info.st_mode)
os.chown(pending, info.st_uid, info.st_gid)
activated = False
try:
    os.replace(pending, index)
    activated = True
    for url in ['/Mobile/', '/Mobile/?installed=1']:
        check_https(url, manifest['indexSha256'])
    for url, expected in [('/', 404), ('/browser/', 401)]:
        with request(url) as response:
            assert response.status == expected, (url, response.status)
except Exception:
    if activated and sha(index) == manifest['indexSha256']:
        rollback = stage / 'index-rollback.html'
        shutil.copy2(backup / 'index-before.html', rollback)
        os.replace(rollback, index)
    raise
manifest.update(status='activated', publishedAtUtc=datetime.datetime.now(datetime.timezone.utc).isoformat(),
                archiveSha256=archive_hash, rollbackHtml=str(backup / 'index-before.html'),
                verification=dict(httpsIndexBothUrls=True, httpsFrameworkHash=True,
                                  reusedCodeFontHashes=True, unchangedDataReferences=True,
                                  root404=True, ownerBrowser401=True))
(backup / 'release.json').write_text(json.dumps(manifest, indent=2))
print(json.dumps(manifest, indent=2))
