#!/usr/bin/env python3
"""Replay the API portions of Bring Five Hard Transactions against your sandbox.

Standard-library only. Writes request/response evidence locally. Never prints or
records the API key. Refuses live/provider keys and cross-origin redirects.
It does not file, pay, activate a subscription, create certificates or infer a pass.
"""
import argparse
import copy
import json
import http.client
import os
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from pathlib import Path
from uuid import uuid4

class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--base-url', default='https://prophit.ai')
    parser.add_argument('--out', default='my-sandbox-evidence.json')
    parser.add_argument('--run', action='store_true', help='Execute sandbox calculations, one sale and one refund')
    parser.add_argument('--customer-code', help='Existing authorized sandbox customer with reviewed certificate')
    parser.add_argument('--certificate-code', help='Existing reviewed synthetic certificate; never a production record')
    args = parser.parse_args()
    if not args.run:
        parser.print_help()
        print('\nNo requests sent. Read README.txt before --run. Supply ITMS_SANDBOX_KEY in your environment.')
        return
    key = os.environ.get('ITMS_SANDBOX_KEY', '')
    if not re.fullmatch(r'itms_test_[A-Za-z0-9._~-]+', key):
        raise SystemExit('An organization sandbox key (itms_test_...) is required. No request sent.')
    origin = urllib.parse.urlsplit(args.base_url)
    if origin.username or origin.password or origin.query or origin.fragment or origin.path not in {'', '/'}:
        raise SystemExit('Use a clean origin without credentials, path or query.')
    if origin.scheme != 'https' and not (origin.scheme == 'http' and origin.hostname in {'localhost', '127.0.0.1'}):
        raise SystemExit('HTTPS is required except for an explicitly selected local sandbox.')
    out = Path(args.out)
    if out.exists():
        raise SystemExit('Choose a new --out file; existing evidence will not be overwritten.')
    opener = urllib.request.build_opener(NoRedirect)
    evidence = {'campaign_id': 'bring-five-hard-transactions', 'pack_version': '1.0.0', 'started_at': datetime.now(timezone.utc).isoformat(), 'base_url': args.base_url, 'records': [], 'assessment': 'unreviewed; an HTTP success is not independent tax validation'}
    def request(label, method, path, payload=None, idem=None):
        headers = {'Authorization': f'Bearer {key}', 'Content-Type': 'application/json', 'User-Agent': 'Prophit-FiveHardTransactions-Sandbox/1.0'}
        if idem:
            headers['Idempotency-Key'] = idem
        record = {'case': label, 'method': method, 'path': path, 'input': payload}
        if idem:
            record['idempotency_key'] = idem
        record['transport_state'] = 'pending'
        evidence['records'].append(record)
        save()  # Persist the exact code/idempotency key before a possibly accepted mutation.
        req = urllib.request.Request(args.base_url.rstrip('/')+path, data=json.dumps(payload).encode() if payload is not None else None, headers=headers, method=method)
        try:
            with opener.open(req, timeout=45) as response:
                status, raw = response.status, response.read(2_000_000).decode('utf-8', errors='replace')
        except urllib.error.HTTPError as error:
            status, raw = error.code, error.read(2_000_000).decode('utf-8', errors='replace')
        except (urllib.error.URLError, TimeoutError, http.client.HTTPException, OSError) as error:
            record['network_error'] = type(error).__name__
            record['transport_state'] = 'uncertain'
            save()
            raise SystemExit('Network outcome uncertain. Evidence saved. Reconcile any created transaction before retrying.') from None
        try:
            body = json.loads(raw)
        except json.JSONDecodeError:
            body = {'non_json_response': raw[:1000]}
        record.update(status=status, output=body, transport_state='response_received')
        save()
        print(f'{label}: HTTP {status}')
        return status, body
    def save():
        out.write_text(json.dumps(evidence, indent=2)+'\n', encoding='utf-8')
    status, context = request('sandbox-context', 'GET', '/api/v1/context')
    if status != 200 or context.get('environment') != 'sandbox' or context.get('credential_type') != 'organization':
        raise SystemExit('A verified organization sandbox context is required. No mutation attempted.')
    status, _ = request('configuration', 'GET', '/api/v1/config')
    if status != 200:
        raise SystemExit('Configuration unavailable. No mutation attempted.')
    status, _ = request('coverage', 'GET', '/api/v1/coverage/states')
    if status != 200:
        raise SystemExit('Coverage unavailable. No mutation attempted.')
    data = json.loads((Path(__file__).parent/'api-executions.json').read_text())
    traces = data['traces']
    unique = uuid4().hex[:12]
    base = copy.deepcopy(traces['sale']['request'])
    base['code'] = f'FHT-{unique}'
    # Exact historic input dates are retained; hosted content/tenant configuration
    # may differ from the archived fixture. Preserve differences, never auto-pass.
    request('missing-product', 'POST', '/api/v1/tax/calculate', traces['missing-product']['request'])
    status, created = request('sale', 'POST', '/api/v1/transactions', base, f'FHT-RETRY-{unique}')
    if status == 201 and created.get('environment') == 'sandbox':
        request('identical-retry', 'POST', '/api/v1/transactions', base, f'FHT-RETRY-{unique}')
        changed = copy.deepcopy(base); changed['lines'][0]['amount'] = '101.00'
        request('conflicting-retry', 'POST', '/api/v1/transactions', changed, f'FHT-RETRY-{unique}')
        refund = copy.deepcopy(traces['partial-refund']['request']); refund['code'] = f'FHT-REFUND-{unique}'
        request('partial-refund', 'POST', f'/api/v1/transactions/{base["code"]}/refund', refund)
    else:
        evidence['lifecycle_skipped'] = 'Sale creation not confirmed in sandbox; replay/refund not attempted.'
    if args.customer_code and args.certificate_code:
        for label in ['valid-certificate', 'expired-certificate']:
            payload = copy.deepcopy(traces[label]['request'])
            payload.update(customer_code=args.customer_code, exemption_certificate_code=args.certificate_code)
            request(label, 'POST', '/api/v1/tax/calculate', payload)
    else:
        evidence['certificate_skipped'] = 'No existing reviewed synthetic certificate/customer supplied. Never count skipped as passed.'
    for address in ['791 RIDGE AVE', '26649 N US HIGHWAY 12']:
        payload = {'transaction_date': '2026-07-26', 'nexus_mode': 'reference', 'accuracy_policy': 'deterministic_only', 'ship_to': {'line1': address, 'city': 'Lake Barrington', 'state': 'IL', 'zip_code': '60084'}, 'lines': [{'number': 1, 'amount': '100.00', 'description': 'Ordinary general retail tangible equipment'}]}
        request('same-zip-address', 'POST', '/api/v1/tax/calculate', payload)
    for row in json.loads((Path(__file__).parent/'responsibility-executions.json').read_text())['rows']:
        payload = copy.deepcopy(traces['sale']['request']); payload.pop('code', None)
        payload['marketplace'] = row['input']
        payload['lines'][0]['amount'] = {'DIRECT-100': '100.00', 'MARKET-200': '200.00', 'UNKNOWN-50': '50.00'}[row['row_id']]
        request(row['row_id'], 'POST', '/api/v1/tax/calculate', payload)
    evidence['completed_at'] = datetime.now(timezone.utc).isoformat()
    save()
    print(f'Evidence saved to {out}. Review outputs, scope and failures before drawing a conclusion.')

if __name__ == '__main__':
    main()
