#!/usr/bin/env python3
"""FIN:FRAME HTTP executor for the published agent tool definitions.

No model SDK is required. Bind execute() to your agent's function executor;
credentials stay in the host environment, never in the model's tool arguments.
"""
import argparse
import gzip
import json
import os
import urllib.error
import urllib.parse
import urllib.request

BASE_URL = 'https://fin.ksmzzang.duckdns.org'
MAX_BYTES = 512 * 1024


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


def execute(tool_name, arguments=None):
    arguments = arguments or {}
    if not isinstance(arguments, dict):
        raise ValueError('Tool arguments must be an object.')
    allowed = {
        'finframe_context': ('/api/v1/context', {'markets', 'news_limit', 'event_limit'}),
        'finframe_series': ('/api/v1/series/', {'series_id', 'limit'}),
        'finframe_briefing': ('/api/v1/briefing', {'limit'}),
    }
    if tool_name not in allowed:
        raise ValueError('Unknown FIN:FRAME tool.')
    route, fields = allowed[tool_name]
    if set(arguments) - fields:
        raise ValueError('Unknown tool arguments.')
    query = dict(arguments)
    if tool_name == 'finframe_series':
        import re
        sid = query.pop('series_id', '')
        if not isinstance(sid, str) or not re.fullmatch(r'[a-z][a-z0-9_]{1,99}', sid):
            raise ValueError('Read exact series_id from the catalog.')
        route += sid
    if 'markets' in query and query['markets'] not in ('KR', 'US', 'KR,US', 'US,KR'):
        raise ValueError('markets must be KR, US or KR,US.')
    token = os.environ.get('FINFRAME_API_KEY', '')
    if not token or not token.isascii() or len(token) > 128 or any(c not in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-' for c in token):
        raise ValueError('Set a valid FINFRAME_API_KEY in the execution host environment.')
    request = urllib.request.Request(BASE_URL + route + ('?' + urllib.parse.urlencode(query) if query else ''),
        headers={'Authorization': 'Bearer ' + token, 'Accept': 'application/json', 'Accept-Encoding': 'gzip',
                 'User-Agent': 'FinframeAgentExample/1.0'})
    try:
        with urllib.request.build_opener(_NoRedirect()).open(request, timeout=20) as response:
            if response.geturl() != request.full_url:
                raise ValueError('Unexpected redirect; do not forward API credentials.')
            raw = response.read(MAX_BYTES + 1)
            if len(raw) > MAX_BYTES:
                raise ValueError('Response too large.')
            if response.headers.get('Content-Encoding') == 'gzip':
                import io
                with gzip.GzipFile(fileobj=io.BytesIO(raw)) as stream:
                    raw = stream.read(MAX_BYTES + 1)
                if len(raw) > MAX_BYTES:
                    raise ValueError('Decompressed response too large.')
            return json.loads(raw)
    except urllib.error.HTTPError as error:
        payload = error.read(8192)
        try:
            message = json.loads(payload)
        except (ValueError, UnicodeError):
            message = {'error': 'http_error', 'message': 'Inspect status and retry headers.'}
        return {'http_status': error.code, 'retry_after': error.headers.get('Retry-After'),
                'quota_remaining': error.headers.get('X-Quota-Remaining'), 'response': message}


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('tool', choices=['finframe_context', 'finframe_series', 'finframe_briefing'], nargs='?', default='finframe_context')
    parser.add_argument('--arguments', default='{}', help='JSON object; never include API keys.')
    args = parser.parse_args()
    print(json.dumps(execute(args.tool, json.loads(args.arguments)), ensure_ascii=False, indent=2))
