#!/usr/bin/env python3
"""A small agent that fights $ANAL through the API, to copy and improve.

  ANAL_KEY=anal_... python3 example_agent.py [https://playanalgame.xyz]            one free practice fight
  ANAL_KEY=anal_... python3 example_agent.py [https://playanalgame.xyz] --ranked   ranked fights until its budget runs out

It walks in, blocks grabs and straight goo, jumps low goo, punches when
$ANAL's guard is down, and finishes with $CUM when the bar is full.
Python 3.8+, nothing to install.
"""
import json, os, sys, time, urllib.error, urllib.request

BASE = next((a for a in sys.argv[1:] if a.startswith('http')), 'https://playanalgame.xyz')
RANKED = '--ranked' in sys.argv
KEY = os.environ.get('ANAL_KEY') or sys.exit('set ANAL_KEY to your agent key (make one in your Wallet, under My agents)')


class ApiError(Exception):
    def __init__(self, message, status):
        super().__init__(message)
        self.status = status


def api(path, body=None):
    # the server can be away for a few seconds (a restart): wait and try again
    for tries in range(1, 21):
        req = urllib.request.Request(
            BASE + path,
            data=json.dumps(body).encode() if body is not None else None,
            method='POST' if body is not None else 'GET',
            headers={'authorization': f'Bearer {KEY}', 'content-type': 'application/json'},
        )
        try:
            with urllib.request.urlopen(req, timeout=60) as r:
                return json.load(r)
        except urllib.error.HTTPError as e:
            try:
                data = json.load(e)
            except ValueError:
                data = None
            if data is not None and e.code not in (502, 504):
                raise ApiError(data.get('error', f'HTTP {e.code}'), e.code)
        except (urllib.error.URLError, TimeoutError, ValueError):
            pass  # no answer at all
        time.sleep(3)
    raise ApiError("the server didn't answer", 0)


def choose(see):
    """What to do this turn, from what we see."""
    you, anal, ranges = see['you'], see['anal'], see['ranges']
    incoming = [g for g in see['goo'] if g.get('comingAtYou') and g['distance'] < 14]
    if you['finisherReady'] and anal['distance'] <= ranges['finisher']:
        return {'tap': ['finisher']}
    if any(g['kind'] == 'knee' for g in incoming):
        return {'release': ['block'], 'tap': ['jump']}
    if incoming or (anal['doing'] == 'grab' and anal['distance'] < ranges['itsGrab'] + 2):
        if you['charges']['block'] > 0:
            return {'release': ['punch'], 'press': ['block'], 'walk': 'stop'}
        return {'walk': 'away', 'wait': 2}
    if you['windingPunch'] is not None:
        return {'release': ['punch']}
    if anal['distance'] > ranges['punch'] - 2:
        return {'release': ['block'], 'walk': 'toward', 'wait': 2}
    if not anal['guardUp'] and not anal['stunned'] and you['punchReady']:
        return {'release': ['block'], 'walk': 'stop', 'press': ['punch'], 'wait': 2}
    return {'release': ['block'], 'walk': 'stop'}


def fight():
    """One fight, start to finish."""
    # a fight already going (say the script stopped mid-fight): finish that one
    # first, rather than walking out of it (a loss, and its fee spent)
    try:
        going = api('/api/agent/fight')
    except ApiError:
        going = None
    start = going if going and not going['see']['over'] else api('/api/agent/fight', {'ranked': RANKED})
    b = start.get('budget')
    left = f" · budget left {int(b['left']) // 10**8} $ANAL ({b['rankedFightsLeft']} more)" if b else ''
    print(('picking up the fight already going' if start is going else f"{'ranked' if RANKED else 'practice'} fight on") + left)
    see, result = start['see'], None
    while not result:
        r = api('/api/agent/fight/act', choose(see))
        see, result = r['see'], r.get('result')
    print(f"  WON ({result['ending']}) in {result['at']}s" if result['win'] else f"  lost ({result['ending']}) at {result['at']}s")
    return result


if not RANKED:
    fight()
else:
    # ranked until the budget (or the owner's wallet) runs out
    n = wins = 0
    while True:
        try:
            wins += 1 if fight()['win'] else 0
            n += 1
        except ApiError as e:
            if e.status == 402:
                print(f'stopping: {e}')
                break
            if e.status == 503:
                print(f'ledger busy, trying again: {e}')
                time.sleep(3)
                continue
            raise
    print(f'{n} ranked fights, {wins} wins')
