"""Publish the exact Stripe and Wise candidates with guarded readback and rollback."""
import datetime
import hashlib
import json
import os
from pathlib import Path
import sys
import urllib.error
import urllib.request

BASE = 'https://n8n.srv1454280.hstgr.cloud/api/v1/workflows/'
PRIVATE = Path('/home/osman/.claude/secrets/halabi-najjar-20261021')
MEMBERS = [
    ('XvxrBN0VVw4iaGW5', 'ai-review-stripe-live-after-rollback-r2.json', 'ai-review-stripe-candidate.json'),
    ('pFgxAp3AOJLNoaX6', 'ai-review-wise-live-before-release.json', 'ai-review-wise-candidate-r3.json'),
]


def api(method, path, body=None):
    raw = None if body is None else json.dumps(body, ensure_ascii=False).encode()
    req = urllib.request.Request(BASE + path, data=raw, method=method, headers={
        'X-N8N-API-KEY': os.environ['N8N_HALABI_API_KEY'],
        'Content-Type': 'application/json',
    })
    try:
        with urllib.request.urlopen(req, timeout=45) as response:
            return json.load(response)
    except urllib.error.HTTPError as error:
        raise RuntimeError(f'n8n {method} {path}: HTTP {error.code}') from None


def normalized_nodes(nodes):
    result = json.loads(json.dumps(nodes))
    for node in result:
        if node['type'] in ('n8n-nodes-base.emailSend', 'n8n-nodes-base.wait'):
            node.pop('webhookId', None)
    return result


def semantic(workflow):
    return {'name': workflow['name'], 'nodes': normalized_nodes(workflow['nodes']),
            'connections': workflow['connections'], 'settings': workflow['settings']}


def payload(workflow):
    settings = workflow['settings']
    return {
        'name': workflow['name'],
        'nodes': workflow['nodes'],
        'connections': workflow['connections'],
        'settings': {key: settings[key] for key in ('executionOrder', 'callerPolicy', 'errorWorkflow') if key in settings},
    }


def digest(workflow):
    raw = json.dumps(semantic(workflow), ensure_ascii=False, sort_keys=True, separators=(',', ':')).encode()
    return hashlib.sha256(raw).hexdigest()


def read_members():
    members = []
    for workflow_id, before_name, candidate_name in MEMBERS:
        before = json.loads((PRIVATE / before_name).read_text())
        candidate = json.loads((PRIVATE / candidate_name).read_text())
        live = api('GET', workflow_id)
        if before['id'] != workflow_id or candidate['id'] != workflow_id:
            raise RuntimeError('Workflow identity mismatch')
        if not live['active'] or semantic(live) != semantic(before) or live['versionId'] != before['versionId']:
            raise RuntimeError('Production workflow drift: ' + workflow_id)
        if candidate['settings'] != before['settings']:
            raise RuntimeError('Unrequested settings change: ' + workflow_id)
        members.append((workflow_id, before, candidate))
    return members


def activate_exact(workflow_id, candidate):
    api('PUT', workflow_id, payload(candidate))
    draft = api('GET', workflow_id)
    if normalized_nodes(draft['nodes']) != normalized_nodes(candidate['nodes']) or draft['connections'] != candidate['connections']:
        raise RuntimeError('Draft mismatch: ' + workflow_id)
    api('POST', workflow_id + '/activate')
    active = api('GET', workflow_id)
    if (not active['active'] or active['activeVersionId'] != active['versionId']
            or normalized_nodes(active['nodes']) != normalized_nodes(candidate['nodes']) or active['connections'] != candidate['connections']
            or active['name'] != candidate['name'] or active['settings'] != candidate['settings']):
        raise RuntimeError('Activation readback mismatch: ' + workflow_id)
    return active


def save_exclusive(name, data):
    path = PRIVATE / name
    with path.open('x') as output:
        json.dump(data, output, ensure_ascii=False, indent=2)
        output.write('\n')
    path.chmod(0o600)


def main():
    if len(sys.argv) != 2 or sys.argv[1] not in ('preflight', 'release'):
        raise SystemExit('Expected preflight or release')
    members = read_members()
    print(json.dumps({'preflight': 'pass', 'members': [
        {'id': workflow_id, 'before_sha256': digest(before), 'candidate_sha256': digest(candidate)}
        for workflow_id, before, candidate in members
    ]}), flush=True)
    if sys.argv[1] == 'preflight':
        return
    changed = []
    receipts = []
    try:
        for workflow_id, before, candidate in members:
            changed.append((workflow_id, before))
            active = activate_exact(workflow_id, candidate)
            receipts.append({'id': workflow_id, 'sha256': digest(active), 'versionId': active['versionId']})
        result = {'released_at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
                  'workflows': receipts, 'production_payment_execution': 'not_tested'}
        save_exclusive('ai-review-payment-routes-release-r3-20261027.json', result)
        print(json.dumps(result), flush=True)
    except Exception as error:
        failures = []
        for workflow_id, before in reversed(changed):
            try:
                restored = activate_exact(workflow_id, before)
                if semantic(restored) != semantic(before):
                    raise RuntimeError('Rollback definition mismatch')
            except Exception as rollback_error:
                failures.append({'id': workflow_id, 'error': str(rollback_error)})
        save_exclusive('ai-review-payment-routes-rollback-r3-20261027.json',
                       {'error': str(error), 'rollback_failures': failures})
        raise


if __name__ == '__main__':
    main()
