#!/usr/bin/env python3
"""
Simple MBAP test harness.
- Loads `data/raw/code/mbap.json`
- For each entry with `code` and `test_list`, runs a heuristic mock AVAP executor
- Compares returned `result` against each test's expected value and prints a summary

Note: This is a heuristic mock executor — it supports many common AVAP idioms but
is not a full AVAP interpreter. It aims to be useful for quick verification.
"""

import json
import re
import sys
import hashlib
import random
import string
from pathlib import Path

MBAP_PATH = Path(__file__).resolve().parents[1] / "data" / "raw" / "code" / "mbap.json"


def load_mbap(path: Path):
    with open(path, "r", encoding="utf-8") as f:
        return json.load(f)


def transform_avap_to_python(code: str) -> str:
    lines = code.splitlines()
    out_lines = []
    for raw in lines:
        line = raw.strip()
        if not line:
            continue
        # remove addParam(...) calls
        if re.match(r'addParam\(', line):
            continue
        # getListLen(var, lenvar) -> lenvar = len(var)
        m = re.match(r'getListLen\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append(f"{m.group(2).strip()} = len({m.group(1).strip()})")
            continue
        # itemFromList(list, idx, var) -> var = list[idx]
        m = re.match(r'itemFromList\(([^,]+),\s*([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append(f"{m.group(3).strip()} = {m.group(1).strip()}[{m.group(2).strip()}]")
            continue
        # AddVariableToJSON(idx, value, result) -> result.append(value)
        m = re.match(r'AddVariableToJSON\(([^,]+),\s*([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append(f"{m.group(3).strip()}.append({m.group(2).strip()})")
            continue
        # startLoop(i, s, e) -> for i in range(s, e+1):
        m = re.match(r'startLoop\(([^,]+),\s*([^,]+),\s*([^,]+)\)', line)
        if m:
            out_lines.append(f"for {m.group(1).strip()} in range({m.group(2).strip()}, {m.group(3).strip()}+1):")
            continue
        # simple else/end tokens
        if line.startswith('else()'):
            out_lines.append('else:')
            continue
        if line.startswith('endLoop') or line == 'end()':
            continue
        # if(a, b, "op")
        m = re.match(r'if\(([^,]*),\s*([^,]*),\s*"([^"]+)"\)', line)
        if m:
            a = m.group(1).strip()
            b = m.group(2).strip()
            op = m.group(3).strip()
            if op in ('==', '!=', '>', '<', '>=', '<='):
                out_lines.append(f"if {a} {op} {b}:")
            else:
                # treat as raw expression
                out_lines.append(f"if {op}:")
            continue
        # addResult(x) -> output = x
        m = re.match(r'addResult\(([^)]+)\)', line)
        if m:
            out_lines.append(f"output = {m.group(1).strip()}")
            continue
        # encodeSHA256(src, dst)
        m = re.match(r'encodeSHA256\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append("import hashlib")
            out_lines.append(f"{m.group(2)} = hashlib.sha256(str({m.group(1)}).encode()).hexdigest()")
            continue
        # encodeMD5
        m = re.match(r'encodeMD5\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append("import hashlib")
            out_lines.append(f"{m.group(2)} = hashlib.md5(str({m.group(1)}).encode()).hexdigest()")
            continue
        # randomString(len, token)
        m = re.match(r'randomString\(([^,]+),\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append(f"{m.group(2)} = ''.join(__random.choice(__letters) for _ in range(int({m.group(1)})))")
            continue
        # getRegex(text, pattern, matches) -> use python re.findall
        m = re.match(r'getRegex\(([^,]+),\s*"([^"]+)",\s*([A-Za-z_][A-Za-z0-9_]*)\)', line)
        if m:
            out_lines.append(f"{m.group(3)} = re.findall(r'{m.group(2)}', str({m.group(1)}))")
            continue
        # RequestGet/Post -> mock
        if line.startswith('RequestGet(') or line.startswith('RequestPost('):
            out_lines.append("response = {'_mock': 'response'}")
            out_lines.append("output = response")
            continue
        # function foo(...) { -> def foo(...):
        m = re.match(r'function\s+([A-Za-z_][A-Za-z0-9_]*)\s*\(([^)]*)\)\s*\{', line)
        if m:
            args = m.group(2).strip()
            out_lines.append(f"def {m.group(1)}({args}):")
            continue
        # return(...) -> return ...
        m = re.match(r'return\(([^)]+)\)', line)
        if m:
            out_lines.append(f"return {m.group(1).strip()}")
            continue
        # simple replacements to make many lines valid python-ish
        line = line.replace('&&', 'and').replace('||', 'or')
        # remove trailing semicolons if any
        if line.endswith(';'):
            line = line[:-1]
        out_lines.append(line)
    # Ensure we always expose 'output' variable at the end if not set
    py = '\n'.join(out_lines)
    guard = "\nif 'output' not in locals():\n    output = locals().get('result', None)\n"
    return py + guard


def mock_execute(code: str, inputs: dict):
    """Heuristic mock executor:
    - Creates a restricted locals dict with provided `inputs`
    - Transforms AVAP-ish code into Python-ish code
    - Executes it and returns {'result': output} or {'error': msg}
    """
    # prepare locals
    globs = {"__builtins__": {}}
    locals_: dict = {}
    # expose helpers
    locals_['re'] = re
    locals_['hashlib'] = hashlib
    locals_['__random'] = random
    locals_['__letters'] = string.ascii_letters + string.digits
    # copy input params into locals
    if isinstance(inputs, dict):
        for k, v in inputs.items():
            locals_[k] = v
    # transform
    py = transform_avap_to_python(code)
    try:
        exec(py, globs, locals_)
        output = locals_.get('output', None)
        return {'result': output}
    except Exception as e:
        return {'error': str(e), 'transformed': py}


def canonical_expected(test_item):
    """Try to extract expected value from a test item.
    Accepts several common shapes: {'input':..., 'expected':...},
    {'in':..., 'out':...}, or simple dicts.
    """
    if isinstance(test_item, dict):
        if 'expected' in test_item:
            return test_item['expected']
        if 'output' in test_item:
            return test_item['output']
        if 'result' in test_item:
            return test_item['result']
        # some datasets embed expected as the last item
        if 'expected_result' in test_item:
            return test_item['expected_result']
    return None


def canonical_input(test_item):
    if isinstance(test_item, dict):
        if 'input' in test_item:
            return test_item['input']
        if 'in' in test_item:
            return test_item['in']
        # if dict and contains params keys other than expected, assume it's the input itself
        # heuristics: if contains keys other than 'expected'/'output' treat as params
        keys = set(test_item.keys())
        if not keys.intersection({'expected','output','result','description'}):
            return test_item
    return {}


def run_all(path: Path):
    data = load_mbap(path)
    total = 0
    passed = 0
    for entry in data:
        task_id = entry.get('task_id')
        code = entry.get('code')
        tests = entry.get('test_list') or []
        if not code:
            continue
        print(f"Task {task_id}: processing {len(tests)} tests")
        for ti, test_item in enumerate(tests):
            total += 1
            inp = canonical_input(test_item)
            expected = canonical_expected(test_item)
            res = mock_execute(code, inp)
            if 'error' in res:
                print(f"  test #{ti+1}: ERROR -> {res['error']}")
                # optionally show transformed code for debugging
                # print(res.get('transformed'))
                continue
            got = res.get('result')
            ok = (expected is None) or (got == expected)
            if ok:
                passed += 1
                status = 'PASS'
            else:
                status = 'FAIL'
            print(f"  test #{ti+1}: {status} | expected={expected!r} got={got!r}")
    print(f"\nSummary: passed {passed}/{total} tests")


if __name__ == '__main__':
    p = MBAP_PATH
    if not p.exists():
        print(f"mbap.json not found at {p}")
        sys.exit(1)
    run_all(p)
