#!/usr/bin/env python3
import base64
import hashlib
import json
import os
import pathlib
import shutil
import socket
import subprocess
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request

LOCAL_API = 'http://127.0.0.1:3000/api/v1'
NETWORK_API = 'https://g.cnnetmkt.top/git/api/v1'
NETWORK_REMOTE = 'https://g.cnnetmkt.top/git/aios/aios.git'
REPO = 'aios/aios'
BASE_BRANCH = 'governance/approvals'
REQUEST_BRANCH = 'governance/request/protection-semantic-reconciliation-v1-20260808'
SOCKET_PATH = '/run/aios-governance-preapproval-publisher/publisher.sock'
CREDENTIAL_NAME = 'gitea-machine-token'
AUTHORITY_REPO = '/data/aios-gitops/repo.git'
TASK_ROOT = pathlib.Path('/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1')
INPUT_ROOT = TASK_ROOT / 'publication-input'
RECEIPT = TASK_ROOT / 'publication-receipt.json'
MANIFEST_REL = 'approvals/governance-policy/AIOS-PROTECTION-SEMANTIC-SNAPSHOT-RECONCILIATION-V1.json'
EVIDENCE_REL = 'approvals/governance-policy/AIOS-PROTECTION-SEMANTIC-SNAPSHOT-RECONCILIATION-V1.evidence.json'


def digest_bytes(data):
    return hashlib.sha256(data).hexdigest()


def digest_json(value):
    return digest_bytes(json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode())


def load_token():
    path = pathlib.Path(os.environ['CREDENTIALS_DIRECTORY']) / CREDENTIAL_NAME
    value = path.read_text(encoding='utf-8').strip()
    if len(value) < 16:
        raise RuntimeError('MACHINE_CREDENTIAL_INVALID')
    return value


def api(base, method, path, token, body=None, expected=(200,)):
    raw = None if body is None else json.dumps(body, separators=(',', ':'), ensure_ascii=False).encode()
    request = urllib.request.Request(base + path, data=raw, method=method)
    request.add_header('Authorization', 'token ' + token)
    request.add_header('Accept', 'application/json')
    if raw is not None:
        request.add_header('Content-Type', 'application/json')
    try:
        with urllib.request.urlopen(request, timeout=20) as response:
            payload = response.read()
            if response.status not in expected:
                raise RuntimeError('GITEA_HTTP_' + str(response.status))
            return response.status, json.loads(payload) if payload else None
    except urllib.error.HTTPError as exc:
        payload = exc.read()
        if exc.code in expected:
            return exc.code, json.loads(payload) if payload else None
        raise RuntimeError('GITEA_HTTP_' + str(exc.code) + ':' + method + ':' + path) from None


def authority_head():
    result = subprocess.run(
        ['git', '--git-dir=' + AUTHORITY_REPO, 'rev-parse', 'refs/heads/' + BASE_BRANCH],
        text=True, capture_output=True, check=False,
    )
    if result.returncode != 0:
        raise RuntimeError('AUTHORITY_BASE_UNAVAILABLE')
    return result.stdout.strip()


def branch_api(base, token, branch, expected=(200,)):
    return api(base, 'GET', '/repos/' + REPO + '/branches/' + urllib.parse.quote(branch, safe=''), token, expected=expected)


def verify_base(token):
    authority = authority_head()
    _, local = branch_api(LOCAL_API, token, BASE_BRANCH)
    _, network = branch_api(NETWORK_API, token, BASE_BRANCH)
    local_oid = local.get('commit', {}).get('id')
    network_oid = network.get('commit', {}).get('id')
    if not authority or authority != local_oid or authority != network_oid:
        raise RuntimeError('RECONCILIATION_REQUIRED')
    return authority


def verify_identity(token):
    _, user = api(LOCAL_API, 'GET', '/user', token)
    if user.get('login') != 'aios-autonomy-agent' or user.get('id') != 153 or user.get('is_admin') is not False:
        raise RuntimeError('MACHINE_IDENTITY_REJECTED')
    return {'login': user['login'], 'id': user['id'], 'owner': user.get('is_admin')}


def load_inputs():
    manifest = json.dumps(json.loads((INPUT_ROOT / 'manifest.json').read_text(encoding='utf-8')), sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode()
    evidence = json.dumps(json.loads((INPUT_ROOT / 'evidence.json').read_text(encoding='utf-8')), sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode()
    for data in (manifest, evidence):
        if any(secret in data.lower() for secret in (b'authorization:', b'ghp_', b'github_pat_', b'secret=', b'token=')):
            raise RuntimeError('SECRET_CONTENT_REJECTED')
    return manifest, evidence


def publication_key(base_oid, manifest_sha, evidence_sha):
    payload = REPO + base_oid + REQUEST_BRANCH + manifest_sha + evidence_sha
    return hashlib.sha256(payload.encode()).hexdigest()


def write_receipt(state, **fields):
    record = {
        'schema': 'aios.governance-preapproval-publication.v1',
        'operation': 'SEMANTIC_RECONCILIATION_REQUEST',
        'state': state,
        'recorded_unix': int(time.time()),
        'request_branch': REQUEST_BRANCH,
        'manifest_path': MANIFEST_REL,
        'evidence_path': EVIDENCE_REL,
        'machine_actor': 'aios-autonomy-agent',
        'owner': False,
        'credential_ref': {'scheme': 'systemd-credential', 'name': CREDENTIAL_NAME},
        'governance_capability_executed': False,
        'protection_mutation': False,
        'force_push': False,
        **fields,
    }
    RECEIPT.parent.mkdir(parents=True, exist_ok=True)
    temporary = RECEIPT.with_suffix('.tmp')
    temporary.write_text(json.dumps(record, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n', encoding='utf-8')
    temporary.replace(RECEIPT)
    RECEIPT.chmod(0o640)
    return record


def existing_prs(token):
    _, prs = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls?state=all&base=' + urllib.parse.quote(BASE_BRANCH, safe='') + '&limit=100', token)
    return [pr for pr in prs if pr.get('head', {}).get('ref') == REQUEST_BRANCH and pr.get('base', {}).get('ref') == BASE_BRANCH]


def verify_existing_branch(token, branch_oid, manifest, evidence):
    _, commit = api(LOCAL_API, 'GET', '/repos/' + REPO + '/git/commits/' + branch_oid, token)
    if commit.get('parents', [{}])[0].get('sha') is None:
        raise RuntimeError('REQUEST_BRANCH_COMMIT_INVALID')
    for relative, expected in ((MANIFEST_REL, manifest), (EVIDENCE_REL, evidence)):
        _, content = api(LOCAL_API, 'GET', '/repos/' + REPO + '/contents/' + urllib.parse.quote(relative, safe='/') + '?ref=' + branch_oid, token)
        actual = base64.b64decode(''.join(content.get('content', '').split()))
        if actual != expected:
            raise RuntimeError('GOVERNANCE_REQUEST_BRANCH_CONFLICT')


def push_one_commit(token, base_oid, manifest, evidence):
    work_root = TASK_ROOT / 'publisher-work'
    work_root.mkdir(parents=True, exist_ok=True)
    temporary = pathlib.Path(tempfile.mkdtemp(prefix='semantic-reconciliation-', dir=work_root))
    askpass = temporary / 'askpass.sh'
    clone = temporary / 'repo'
    credential_path = pathlib.Path(os.environ['CREDENTIALS_DIRECTORY']) / CREDENTIAL_NAME
    askpass.write_text(
        '#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR";; *) cat "$CREDENTIAL_FILE";; esac\n',
        encoding='utf-8',
    )
    askpass.chmod(0o700)
    env = os.environ.copy()
    env.update({
        'GIT_ASKPASS': str(askpass),
        'GIT_TERMINAL_PROMPT': '0',
        'GITEA_MACHINE_ACTOR': 'aios-autonomy-agent',
        'CREDENTIAL_FILE': str(credential_path),
        'GIT_CONFIG_COUNT': '1',
        'GIT_CONFIG_KEY_0': 'safe.directory',
        'GIT_CONFIG_VALUE_0': AUTHORITY_REPO,
    })
    try:
        run = subprocess.run(
            ['git', 'clone', '--branch', BASE_BRANCH, '--single-branch', '--quiet', NETWORK_REMOTE, str(clone)],
            text=True, capture_output=True, env=env, check=False,
        )
        if run.returncode != 0:
            detail = ' '.join(run.stderr.strip().split())[:240]
            raise RuntimeError('PUBLISHER_CLONE_FAILED:' + detail)
        def git(*args, push_env=None):
            result = subprocess.run(['git', *args], cwd=clone, text=True, capture_output=True, env=push_env or env, check=False)
            if result.returncode != 0:
                raise RuntimeError('PUBLISHER_GIT_OPERATION_FAILED')
            return result.stdout.strip()
        git('checkout', '--detach', base_oid)
        git('switch', '--create', REQUEST_BRANCH)
        manifest_path = clone / MANIFEST_REL
        evidence_path = clone / EVIDENCE_REL
        manifest_path.parent.mkdir(parents=True, exist_ok=True)
        evidence_path.parent.mkdir(parents=True, exist_ok=True)
        manifest_path.write_bytes(manifest)
        evidence_path.write_bytes(evidence)
        git('add', '--', MANIFEST_REL, EVIDENCE_REL)
        staged = git('diff', '--cached', '--name-only').splitlines()
        if staged != sorted([MANIFEST_REL, EVIDENCE_REL]):
            raise RuntimeError('PUBLISHER_PATH_ALLOWLIST_FAILED')
        git('-c', 'user.name=aios-autonomy-agent', '-c', 'user.email=aios-autonomy-agent@aios.invalid', 'commit', '--quiet', '-m', '[governance] publish semantic reconciliation evidence')
        if git('rev-list', '--count', base_oid + '..HEAD') != '1':
            raise RuntimeError('PUBLISHER_COMMIT_COUNT_FAILED')
        git('push', '--porcelain', NETWORK_REMOTE, 'HEAD:refs/heads/' + REQUEST_BRANCH, push_env=env)
        head = git('rev-parse', 'HEAD')
        return head
    finally:
        shutil.rmtree(temporary, ignore_errors=True)


def publish():
    token = load_token()
    identity = verify_identity(token)
    base_oid = verify_base(token)
    manifest, evidence = load_inputs()
    manifest_sha = digest_bytes(manifest)
    evidence_sha = digest_bytes(evidence)
    key = publication_key(base_oid, manifest_sha, evidence_sha)
    write_receipt('REQUESTED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, machine_identity=identity)
    write_receipt('LOCAL_CANDIDATE_VERIFIED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, machine_identity=identity)

    branch_status, branch = branch_api(LOCAL_API, token, REQUEST_BRANCH, expected=(200, 404))
    if branch_status == 200:
        branch_oid = branch.get('commit', {}).get('id')
        verify_existing_branch(token, branch_oid, manifest, evidence)
    else:
        branch_oid = push_one_commit(token, base_oid, manifest, evidence)
        verify_existing_branch(token, branch_oid, manifest, evidence)
    write_receipt('BRANCH_PUSHED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, machine_identity=identity)

    prs = existing_prs(token)
    if len(prs) > 1:
        raise RuntimeError('MULTIPLE_MATCHING_APPROVAL_PRS')
    if prs:
        pr = prs[0]
        if pr.get('head', {}).get('sha') != branch_oid:
            raise RuntimeError('APPROVAL_PR_HEAD_MISMATCH')
        pr_number = pr.get('number')
    else:
        _, pr = api(LOCAL_API, 'POST', '/repos/' + REPO + '/pulls', token, {
            'title': '[GOVERNANCE RECONCILIATION] Semantic protection snapshot schema migration',
            'head': REQUEST_BRANCH,
            'base': BASE_BRANCH,
            'body': 'Schema-only reconciliation for the existing PR54 Stage-1 and Stage-2 predicates. No new governance capability; Owner exact-head re-attestation required.',
        }, expected=(201,))
        pr_number = pr.get('number')
    write_receipt('PR_CREATED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, pr_number=pr_number, machine_identity=identity)

    _, remote_branch = branch_api(NETWORK_API, token, REQUEST_BRANCH)
    if remote_branch.get('commit', {}).get('id') != branch_oid:
        raise RuntimeError('NETWORK_BRANCH_READBACK_MISMATCH')
    _, read_pr = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number), token)
    if read_pr.get('head', {}).get('sha') != branch_oid or read_pr.get('base', {}).get('ref') != BASE_BRANCH:
        raise RuntimeError('PR_READBACK_MISMATCH')
    result = write_receipt('COMPLETED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, pr_number=pr_number, pr_head_oid=read_pr.get('head', {}).get('sha'), machine_identity=identity)
    return result


def self_test():
    token = load_token()
    identity = verify_identity(token)
    base_oid = verify_base(token)
    branch_status, _ = branch_api(LOCAL_API, token, REQUEST_BRANCH, expected=(200, 404))
    return {
        'status': 'PASS',
        'governance_base_oid': base_oid,
        'machine_identity': identity,
        'credential_ref': {'scheme': 'systemd-credential', 'name': CREDENTIAL_NAME},
        'branch_namespace': 'refs/heads/governance/request/*',
        'target_base': BASE_BRANCH,
        'request_branch_status': 'EXISTS' if branch_status == 200 else 'ABSENT',
        'operation_allowlist': ['READ_BASE', 'CREATE_ONE_REQUEST_BRANCH', 'WRITE_TWO_EVIDENCE_PATHS_ONE_COMMIT', 'CREATE_OR_READ_ONE_APPROVAL_PR', 'READBACK'],
        'denied': ['MAIN_WRITE', 'DIRECT_APPROVALS_WRITE', 'BRANCH_PROTECTION', 'MERGE', 'FORCE_PUSH', 'TAG', 'PRODUCTION', 'CREDENTIAL_MUTATION'],
        'governance_capability_executed': False,
    }


def read_publication_status():
    token = load_token()
    receipt = json.loads(RECEIPT.read_text(encoding='utf-8'))
    pr_number = receipt.get('pr_number')
    if not pr_number:
        raise RuntimeError('PUBLICATION_RECEIPT_PR_MISSING')
    _, pr = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number), token)
    _, reviews = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number) + '/reviews', token)
    _, branch = branch_api(LOCAL_API, token, REQUEST_BRANCH)
    _, network_branch = branch_api(NETWORK_API, token, REQUEST_BRANCH)
    return {
        'status': 'PASS',
        'request_branch': REQUEST_BRANCH,
        'branch_head': branch.get('commit', {}).get('id'),
        'network_branch_head': network_branch.get('commit', {}).get('id'),
        'pr_number': pr.get('number'),
        'pr_state': pr.get('state'),
        'pr_merged': pr.get('merged'),
        'pr_head': pr.get('head', {}).get('sha'),
        'pr_base': pr.get('base', {}).get('ref'),
        'reviews': [
            {
                'id': item.get('id'),
                'reviewer_id': item.get('user', {}).get('id'),
                'state': item.get('state'),
                'commit_id': item.get('commit_id'),
                'dismissed': item.get('dismissed'),
            }
            for item in reviews
        ],
        'governance_capability_executed': False,
        'protection_mutation': False,
    }


def handle(request):
    if not isinstance(request, dict) or set(request) != {'operation'}:
        raise RuntimeError('REQUEST_SCHEMA_INVALID')
    if request['operation'] == 'DRY_RUN_SELF_TEST':
        return self_test()
    if request['operation'] == 'PUBLISH_SEMANTIC_RECONCILIATION':
        return publish()
    if request['operation'] == 'READ_SEMANTIC_RECONCILIATION_STATUS':
        return read_publication_status()
    raise RuntimeError('OPERATION_DENIED')


def serve(listener):
    conn, _ = listener.accept()
    with conn:
        try:
            data = conn.recv(1024 * 1024 + 1)
            if len(data) > 1024 * 1024:
                raise RuntimeError('REQUEST_TOO_LARGE')
            result = handle(json.loads(data.decode()))
        except Exception as exc:
            result = {'status': 'FAIL', 'code': str(exc)}
        conn.sendall((json.dumps(result, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n').encode())


def main():
    if os.environ.get('LISTEN_FDS') != '1':
        raise SystemExit('SOCKET_ACTIVATION_REQUIRED')
    listener = socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
    serve(listener)


if __name__ == '__main__':
    main()
