#!/usr/bin/env python3
"""Harness Compute preview client. Python 3 standard library only."""
import argparse
import json
import os
from pathlib import Path
import sys
import urllib.error
import urllib.parse
import urllib.request
import uuid


def main():
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument('--config', default='harness.json', help='Private connection file')
    sub = p.add_subparsers(dest='command', required=True)
    init = sub.add_parser('init', help='Create a free workspace without an account')
    init.add_argument('--url', default='https://harnesscomputepreview-api-first-test-4wbyu4kws3ekyzc2.manoscasey.workers.dev')
    for command in ('doctor', 'records', 'tools', 'smoke'):
        sub.add_parser(command)
    record = sub.add_parser('record')
    record.add_argument('id'); record.add_argument('title'); record.add_argument('--version', type=int, default=0)
    tool = sub.add_parser('publish')
    tool.add_argument('id'); tool.add_argument('file'); tool.add_argument('--version', type=int, default=0)
    run = sub.add_parser('run'); run.add_argument('id'); run.add_argument('--input', default='{}')
    revoke = sub.add_parser('revoke'); revoke.add_argument('id')
    args = p.parse_args()
    if args.command == 'init':
        if Path(args.config).exists():
            sys.exit('Connection file already exists. Use doctor to reconnect, or choose a different --config path.')
        base = args.url.rstrip('/')
        parsed = urllib.parse.urlsplit(base)
        if parsed.scheme != 'https' and parsed.hostname not in ('localhost', '127.0.0.1'):
            sys.exit('HTTPS required for remote setup')
        class SetupNoRedirect(urllib.request.HTTPRedirectHandler):
            def redirect_request(self, *a, **kw): return None
        request = urllib.request.Request(base + '/workspaces', data=b'{}', headers={'Content-Type':'application/json','User-Agent':'HarnessCompute-Preview/0.1'}, method='POST')
        try:
            with urllib.request.build_opener(SetupNoRedirect()).open(request, timeout=20) as response:
                created = json.load(response)
            with os.fdopen(os.open(args.config, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), 'w') as file:
                json.dump({'url':base,'token':created['token']}, file, indent=2)
            print(json.dumps({'workspace':created['workspace'],'free':True,'connectionFile':args.config,'next':'python3 client.py smoke'},indent=2))
        except urllib.error.HTTPError as e:
            try: message=json.load(e).get('error','Setup failed')
            except ValueError: message='Setup failed'
            sys.exit(f'{e.code}: {message}')
        except (OSError, ValueError, KeyError) as e:
            sys.exit('Setup failed: ' + str(e))
        return
    try:
        config = json.loads(Path(args.config).read_text())
        base, key = config['url'].rstrip('/'), config['token']
        parsed = urllib.parse.urlsplit(base)
        if parsed.scheme != 'https' and parsed.hostname not in ('localhost', '127.0.0.1'):
            raise ValueError('HTTPS is required for remote connections')
        if parsed.username or parsed.password or parsed.query or parsed.fragment:
            raise ValueError('Connection URL must not contain credentials, query or fragment')
    except (OSError, ValueError, KeyError) as e:
        sys.exit('Connection file missing or invalid. Get your private harness.json from the owner. ' + str(e))

    class NoRedirect(urllib.request.HTTPRedirectHandler):
        def redirect_request(self, *a, **kw):
            return None
    opener = urllib.request.build_opener(NoRedirect())
    def request(path, method='GET', body=None, authenticated=True):
        data = None if body is None else json.dumps(body).encode()
        headers = {'Content-Type': 'application/json', 'User-Agent': 'HarnessCompute-Preview/0.1'}
        if authenticated: headers['Authorization'] = 'Bearer ' + key
        req = urllib.request.Request(base + path, data=data, headers=headers, method=method)
        try:
            with opener.open(req, timeout=20) as response:
                return response.status, json.load(response)
        except urllib.error.HTTPError as e:
            try: return e.code, json.load(e)
            except ValueError: return e.code, {'error': 'Non-JSON server error'}
    def expect(path, method='GET', body=None, status=200, authenticated=True):
        actual, value = request(path, method, body, authenticated)
        if actual != status:
            raise RuntimeError(f'{method} {path}: expected {status}, got {actual}: {value}')
        return value

    try:
        if args.command == 'doctor':
            result = {'health': expect('/health', authenticated=False), 'workspace': expect('/workspace')}
        elif args.command == 'records': result = expect('/records')
        elif args.command == 'tools': result = expect('/tools')
        elif args.command == 'record': result = expect('/records', 'POST', {'id': args.id, 'title': args.title, 'expectedVersion': args.version})
        elif args.command == 'publish': result = expect('/tools', 'POST', {'id': args.id, 'code': Path(args.file).read_text(), 'expectedVersion': args.version})
        elif args.command == 'run': result = expect('/run/' + urllib.parse.quote(args.id, safe=''), 'POST', json.loads(args.input))
        elif args.command == 'revoke': result = expect('/tools/' + urllib.parse.quote(args.id, safe=''), 'DELETE')
        elif args.command == 'smoke':
            # Reuses bounded fixture IDs so repeated checks do not consume the workspace.
            record_id, tool_id = 'welcome-record', 'welcome-summary'
            records = expect('/records')['records']
            version = next((r['version'] for r in records if r['id'] == record_id), 0)
            expect('/records', 'POST', {'id': record_id, 'title': 'My first lasting agent tool', 'expectedVersion': version})
            tool = next((t for t in expect('/tools')['tools'] if t['id'] == tool_id), None)
            code = 'export default { async fetch(req, env) { const {records,input} = JSON.parse(env.INPUT); return Response.json({titles: records.map(r=>r.title), note: input.note ?? "Saved in the cloud"}); } };'
            saved = expect('/tools', 'POST', {'id': tool_id, 'code': code, 'expectedVersion': tool['version'] if tool else 0})
            result = expect('/run/' + tool_id, 'POST', {'note': 'Connected from a fresh client'})
            assert 'My first lasting agent tool' in result['result']['titles']
            assert result['result']['note'] == 'Connected from a fresh client'
            result = {'passed': True, 'tool': tool_id, 'version': saved['version'], 'result': result['result'], 'next': 'Start a fresh agent with this same connection file. Run: python3 client.py run welcome-summary'}
        print(json.dumps(result, indent=2))
    except (OSError, ValueError, RuntimeError, AssertionError) as e:
        sys.exit('Check failed: ' + str(e))

if __name__ == '__main__': main()
