"""Initialize only a fresh, task-owned local Gitea, saving generated credentials privately."""
import argparse
import base64
import json
import os
from pathlib import Path
import re
import subprocess
import urllib.request


def docker(*args):
    result = subprocess.run(['docker', *args], capture_output=True, text=True)
    if result.returncode:
        raise RuntimeError('Docker operation failed; inspect the dedicated instance locally')
    return result.stdout


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, *args, **kwargs):
        return None


def main():
    p = argparse.ArgumentParser()
    p.add_argument('--container', required=True)
    p.add_argument('--project', required=True)
    p.add_argument('--port', type=int, default=18091)
    p.add_argument('--private-dir', default='private')
    args = p.parse_args()
    info = json.loads(docker('inspect', args.container))[0]
    labels = info['Config'].get('Labels') or {}
    if (labels.get('com.docker.compose.project') != args.project or
            labels.get('com.docker.compose.service') != 'gitea'):
        raise RuntimeError('Container must be this lab project\'s gitea service')
    if info['Config']['Image'].split('@')[0] != 'docker.gitea.com/gitea:1.27.3-rootless':
        raise RuntimeError('Unexpected Gitea image')
    bindings = info['NetworkSettings']['Ports'].get('3000/tcp') or []
    if not any(x['HostIp'] == '127.0.0.1' and x['HostPort'] == str(args.port)
               for x in bindings):
        raise RuntimeError('Expected loopback-only Gitea port mapping is absent')
    prefix = ['exec', args.container, 'gitea', '--config', '/etc/gitea/app.ini', 'admin', 'user']
    users = docker(*prefix, 'list').strip().splitlines()
    if len(users) != 1 or not users[0].startswith('ID'):
        raise RuntimeError('Gitea already has users; initialization was not performed')
    os.umask(0o077)
    private = Path(args.private_dir)
    private.mkdir(mode=0o700, parents=False, exist_ok=False)
    credentials = {}
    for role in ('owner', 'reader', 'bot'):
        name = 'flux-' + role
        output = docker(*prefix, 'create', '--username', name,
                        '--email', role + '@example.invalid', '--random-password',
                        '--must-change-password=false')
        # Preserve recovery information before parsing, never print it to a shared log.
        (private / (role + '-creation.txt')).write_text(output)
        match = re.search(r"^generated random password is '(.*)'$", output, re.M)
        if not match:
            raise RuntimeError('Unexpected output; retain the private directory and inspect locally')
        credentials[role] = {'user': name, 'password': match[1]}
        (private / (role + '-username')).write_text(name)
        (private / (role + '-password')).write_text(match[1])
        (private / 'credentials.json').write_text(json.dumps(credentials))
    owner = credentials['owner']
    auth = base64.b64encode((owner['user'] + ':' + owner['password']).encode()).decode()
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())

    def api(method, endpoint, data):
        request = urllib.request.Request(
            f'http://127.0.0.1:{args.port}/api/v1' + endpoint,
            data=json.dumps(data).encode(), method=method,
            headers={'Authorization': 'Basic ' + auth, 'Content-Type': 'application/json'})
        with opener.open(request, timeout=15) as response:
            if response.status not in (200, 201, 204):
                raise RuntimeError('Unexpected Gitea API response')

    api('POST', '/user/repos', {'name': 'warehouse', 'private': True,
                              'auto_init': False, 'default_branch': 'main'})
    for user, permission in [('flux-reader', 'read'), ('flux-bot', 'write')]:
        api('PUT', '/repos/flux-owner/warehouse/collaborators/' + user,
            {'permission': permission})
    print('Created private warehouse repository: owner, read-only reader, write collaborator')
    print('Credentials saved privately; main branch protection must be configured separately')


if __name__ == '__main__':
    main()
