"""Ubuntu/Apache installer with configuration snapshots and failure rollback."""
import argparse
import datetime
import json
import os
import re
import secrets
import shutil
import subprocess
import time
from pathlib import Path
from urllib.request import urlopen

FILES = ['provider.py', 'catalog.py', 'quality.py', 'server.py', 'reference-crypto.rs', 'UPSTREAM-LICENSE.txt', 'test_addon.py', 'test_quality.py', 'live_check.py']
APP = Path('/opt/moviebox-addon')
ENV = Path('/etc/moviebox-addon/service.env')
SERVICE = Path('/etc/systemd/system/moviebox-addon.service')
INCLUDE = Path('/etc/apache2/moviebox-addon.inc')


def command(*args, check=True):
    return subprocess.run(args, check=check, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)


def site_config(text, include=False, legacy=False):
    if include:
        if text.count('</VirtualHost>') != 1:
            raise ValueError('Expected exactly one VirtualHost in SSL configuration')
        if legacy:
            text = re.sub(r'^\s*Include /etc/apache2/moviebox-addon-test\.inc\s*$', '', text, flags=re.M)
        text = re.sub(r'^\s*Include /etc/apache2/moviebox-addon\.inc\s*$', '', text, flags=re.M)
        text = text.replace('</VirtualHost>', '    Include /etc/apache2/moviebox-addon.inc\n</VirtualHost>')
    lines=[]
    for line in text.splitlines():
        if re.match(r'\s*CustomLog\s', line):
            if 'env=' in line and 'env=!moviebox_private' not in line:
                raise ValueError('CustomLog already has another condition; configure exclusion manually')
            if 'env=' not in line:
                line += ' env=!moviebox_private'
        lines.append(line)
    text='\n'.join(lines)+'\n'
    rule='    SetEnvIf Request_URI "^/moviebox/" moviebox_private\n'
    if rule.strip() not in text:
        text=text.replace('</VirtualHost>',rule+'</VirtualHost>')
    if legacy and 'SetEnvIf Request_URI "^/moviebox-test/" moviebox_private' not in text:
        text=text.replace('</VirtualHost>','    SetEnvIf Request_URI "^/moviebox-test/" moviebox_private\n</VirtualHost>')
    return text


def main():
    parser=argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--domain', required=True)
    parser.add_argument('--ssl-vhost', required=True, help='Existing HTTPS virtual-host file under /etc/apache2/sites-available')
    parser.add_argument('--http-vhost', help='Optional HTTP redirect vhost; excludes access-token paths from logs')
    args=parser.parse_args()
    if os.geteuid()!=0:
        parser.error('Run using sudo/root')
    os.umask(0o077)
    if not re.fullmatch(r'[a-zA-Z0-9][a-zA-Z0-9.-]{0,250}', args.domain):
        parser.error('Invalid domain')
    ssl=Path(args.ssl_vhost).resolve()
    http=Path(args.http_vhost).resolve() if args.http_vhost else None
    for path in [ssl]+([http] if http else []):
        if path.parent != Path('/etc/apache2/sites-available') or not path.is_file():
            parser.error('Use an existing vhost under /etc/apache2/sites-available')
    if APP.is_symlink():
        parser.error('Application target may not be a symlink')
    source=Path(__file__).resolve().parent
    for name in FILES:
        if not (source/name).is_file():
            parser.error('Missing package file '+name)
    command('apache2ctl','configtest')
    unit_test=command('python3','-m','unittest','discover','-s',str(source),'-p','test_*.py','-v')
    print('Package tests passed',flush=True)
    old_active=command('systemctl','is-active','moviebox-addon',check=False).returncode==0
    old_enabled=command('systemctl','is-enabled','moviebox-addon',check=False).returncode==0
    if not old_active:
        import socket
        with socket.socket() as sock:
            try: sock.bind(('127.0.0.1',8766))
            except OSError: parser.error('Port 8766 is occupied by another process')
    token=None
    for path in [ENV]:
        if path.exists():
            for line in path.read_text().splitlines():
                if line.startswith(('MOVIEBOX_TOKEN=','MOVIEBOX_TEST_TOKEN=')):
                    token=line.split('=',1)[1].strip()
            if token: break
    token=token or secrets.token_urlsafe(32)
    if not re.fullmatch(r'[A-Za-z0-9_-]{32,200}',token):
        parser.error('Existing token has an unsupported format')
    stamp=datetime.datetime.now(datetime.timezone.utc).strftime('%Y%m%d-%H%M%S')+'-'+secrets.token_hex(2)
    backup=Path('/root/moviebox-addon-backups')/stamp
    backup.mkdir(parents=True,mode=0o700)
    tracked=[ssl,INCLUDE,SERVICE,ENV]+([http] if http else [])+[APP/name for name in FILES]
    saved={}
    for index,path in enumerate(tracked):
        if path.exists():
            copy=backup/str(index); shutil.copy2(path,copy); saved[path]=copy
        else:
            saved[path]=None
    mapping={str(path):str(copy) if copy else None for path,copy in saved.items()}
    (backup/'restore-map.json').write_text(json.dumps(mapping,indent=2))
    try:
        if command('id','moviebox-addon',check=False).returncode:
            command('adduser','--system','--group','--no-create-home','moviebox-addon')
        APP.mkdir(mode=0o755,exist_ok=True); APP.chmod(0o755)
        for name in FILES:
            target=APP/name; shutil.copyfile(source/name,target); target.chmod(0o644)
        ENV.parent.mkdir(mode=0o700,exist_ok=True)
        ENV.write_text('MOVIEBOX_TOKEN='+token+'\nMOVIEBOX_PORT=8766\nMOVIEBOX_RATE_PER_IP=240\nMOVIEBOX_STATE=/var/lib/moviebox-addon\nMOVIEBOX_PUBLIC_BASE=https://'+args.domain+'\n'); ENV.chmod(0o600)
        SERVICE.write_text('''[Unit]
Description=MovieBox catalog and stream add-on
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=moviebox-addon
Group=moviebox-addon
WorkingDirectory=/opt/moviebox-addon
EnvironmentFile=/etc/moviebox-addon/service.env
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -u /opt/moviebox-addon/server.py
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
StateDirectory=moviebox-addon
StateDirectoryMode=0700
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
MemoryMax=256M
CPUQuota=50%
TasksMax=40
Nice=10
UMask=0077
[Install]
WantedBy=multi-user.target
'''); SERVICE.chmod(0o644)
        paths=['moviebox']
        snippet=''
        for prefix in paths:
            snippet+=f'''ProxyPass "/{prefix}/" "http://127.0.0.1:8766/" connectiontimeout=3 timeout=90 retry=0
ProxyPassReverse "/{prefix}/" "http://127.0.0.1:8766/"
<Location "/{prefix}/">
    Require all granted
    LogLevel crit
</Location>
'''
        INCLUDE.write_text(snippet); INCLUDE.chmod(0o644)
        ssl.write_text(site_config(ssl.read_text(),True))
        if http: http.write_text(site_config(http.read_text(),False))
        command('a2enmod','proxy','proxy_http','setenvif')
        command('apache2ctl','configtest')
        command('systemctl','daemon-reload')
        command('systemctl','restart','moviebox-addon')
        for attempt in range(15):
            try:
                with urlopen('http://127.0.0.1:8766/'+token+'/health',timeout=3) as r:
                    assert json.load(r)['version']=='1.1.3'
                break
            except Exception:
                if attempt==14: raise RuntimeError('New service did not become healthy') from None
                time.sleep(.5)
        command('systemctl','reload','apache2')
        with urlopen('https://'+args.domain+'/moviebox/'+token+'/manifest.json',timeout=20) as r:
            manifest=json.load(r); assert manifest['version']=='1.1.3' and len(manifest['catalogs'])==6
        command('systemctl','enable','moviebox-addon')
    except Exception as error:
        for path,copy in saved.items():
            if copy: path.parent.mkdir(parents=True,exist_ok=True); shutil.copy2(copy,path)
            else: path.unlink(missing_ok=True)
        command('systemctl','daemon-reload',check=False)
        if old_active: command('systemctl','restart','moviebox-addon',check=False)
        else: command('systemctl','stop','moviebox-addon',check=False)
        if not old_enabled: command('systemctl','disable','moviebox-addon',check=False)
        command('apache2ctl','configtest',check=False)
        command('systemctl','reload','apache2',check=False)
        print('Installation failed; prior files restored. Review root-only backup: '+str(backup),flush=True)
        # Exception messages can contain a tokenized URL; deliberately do not print them.
        raise SystemExit(1) from None
    link=Path('/root/moviebox-addon-link.txt')
    link.write_text('MovieBox manifest (keep private):\nhttps://'+args.domain+'/moviebox/'+token+'/manifest.json\n')
    link.chmod(0o600)
    print('Installed MovieBox 1.1.3. Six catalogs; private HTTPS manifest verified.',flush=True)
    print('Manifest URL saved in /root/moviebox-addon-link.txt (not printed).',flush=True)
    print('Configuration backup: '+str(backup),flush=True)


if __name__=='__main__': main()
