import os, json, re

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
API_DIR = os.path.join(SCRIPT_DIR, 'data', 'magic-api', 'api')

def parse_ms_file(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()
    parts = content.split('================================\n', 1)
    if len(parts) != 2:
        return None, None, content
    try:
        header = json.loads(parts[0].strip())
    except Exception:
        return None, None, content
    return header, parts[1], content

def write_ms_file(filepath, header, script):
    new_content = json.dumps(header, ensure_ascii=False, indent=2)
    new_content += '\n================================\n' + script
    with open(filepath, 'w', encoding='utf-8') as f:
        f.write(new_content)

def get_code_text(script):
    """Extract only code parts (outside triple-quoted strings)."""
    parts = re.split(r'("""|\'\'\')', script)
    result = []
    in_string = False
    for part in parts:
        if part in ('"""', "'''"):
            in_string = not in_string
        elif not in_string:
            result.append(part)
    return ''.join(result)

def apply_fix_to_code(text, fields):
    """Add body. prefix to bare field references in code text."""
    sorted_fields = sorted(fields, key=lambda f: (-len(f), f))
    for field in sorted_fields:
        text = re.sub(
            r'(?<!body\.)'
            r'(?<![\w.\${#])'
            r'(' + re.escape(field) + r')'
            r'(?![\w])',
            r'body.\1',
            text
        )
    return text

# ============================================================
# Step 1: Fix GET files - remove self-assignment lines
# ============================================================
get_fixed = 0
for root, dirs, files in os.walk(API_DIR):
    for fname in sorted(files):
        if not fname.endswith('.ms'):
            continue
        fpath = os.path.join(root, fname)
        header, script, content = parse_ms_file(fpath)
        if header is None or header.get('method') != 'GET':
            continue

        old_script = script

        # Remove lines that are ONLY: var xxx = xxx;
        script = re.sub(
            r'^(\s*)var\s+(\w+)\s*=\s*\2\s*;\s*\n?',
            r'',
            script,
            flags=re.MULTILINE
        )
        script = re.sub(r';var\s+(\w+)\s*=\s*\1\s*;', r';', script)

        # Clean up
        script = re.sub(r';;+', r';', script)
        script = re.sub(r'^\s*\n', r'', script, flags=re.MULTILINE)

        if script != old_script:
            write_ms_file(fpath, header, script)
            get_fixed += 1
            print(f'  Fixed GET: {os.path.relpath(fpath, SCRIPT_DIR)}')

print(f'GET files fixed: {get_fixed}')

# ============================================================
# Step 2: Fix POST files
# ============================================================
post_fixed = 0
for root, dirs, files in os.walk(API_DIR):
    for fname in sorted(files):
        if not fname.endswith('.ms'):
            continue
        fpath = os.path.join(root, fname)
        header, script, content = parse_ms_file(fpath)
        if header is None or header.get('method') != 'POST':
            continue

        old_script = script

        code_text = get_code_text(script)

        # Collect body field names from var declarations: var xxx = body.yyy;
        body_fields = set()
        for m in re.finditer(r'var\s+(\w+)\s*=\s*body\.(\w+)\s*;', code_text):
            body_fields.add(m.group(2))

        # Collect local variable names
        local_vars = set()
        for m in re.finditer(r'var\s+(\w+)\s*(?:=|;)', code_text):
            local_vars.add(m.group(1))

        # Also collect variable names from for-in loops: for (val in xxx)
        # ('val' is a common loop var, we should track it)
        for m in re.finditer(r'for\s*\(\s*(\w+)\s+in\s+', code_text):
            local_vars.add(m.group(1))

        # Keywords/globals/functions — NOT body fields
        skip_words = {'true', 'false', 'null', 'now', 'new', 'return', 'if', 'else',
                      'for', 'in', 'var', 'function', 'import', 'exit', 'assert',
                      'db', 'StpUtil', 'BCryptPasswordEncoder',
                      'getLoginIdAsLong', 'getTokenInfo', 'transaction',
                      'select', 'selectOne', 'selectInt', 'update', 'insert',
                      'size', 'push', 'remove', 'matches', 'encode', 'login',
                      'val', 'item', 'children', 'tree', 'list', 'result',
                      'userId', 'roleId', 'deptId', 'parentId', 'categoryType',
                      'actionType', 'consumableId', 'operatorId', 'stocktakeId',
                      'roleIds', 'ids', 'menuIds', 'rolesArr', 'permsArr',
                      'tokenInfo', 'encoder', 'hashedPassword', 'newPassword'}

        # Find potential body field names:
        # 1. Snake_case names in assertions/conditions
        for m in re.finditer(r'(?:assert|if)\s*\(?\s*(\w+)', code_text):
            name = m.group(1)
            if (name not in local_vars and name not in skip_words
                    and name != 'body' and '_' in name):
                body_fields.add(name)

        # 2. Names used in function calls that look like body fields
        # Pattern: func(body_field_name) — single-word names that aren't local vars
        for m in re.finditer(r'\.(\w+)\(\s*(\w+)\s*[,)]', code_text):
            name = m.group(2)
            if name not in local_vars and name not in skip_words:
                body_fields.add(name)

        # 3. All remaining body.xxx references
        for m in re.finditer(r'(?<!\w)body\.(\w+)', code_text):
            body_fields.add(m.group(1))

        if not body_fields:
            continue

        # Process: split by triple-quoted strings, fix code parts only
        parts = re.split(r'("""|\'\'\')', script)
        result_parts = []
        in_string = False
        for part in parts:
            if part in ('"""', "'''"):
                in_string = not in_string
                result_parts.append(part)
            elif in_string:
                result_parts.append(part)
            else:
                result_parts.append(apply_fix_to_code(part, body_fields))

        script = ''.join(result_parts)

        if script != old_script:
            write_ms_file(fpath, header, script)
            post_fixed += 1
            print(f'  Fixed POST: {os.path.relpath(fpath, SCRIPT_DIR)}')

print(f'POST files fixed: {post_fixed}')
print(f'\nTotal: GET fixed={get_fixed}, POST fixed={post_fixed}')
