import t, c
from stdint import *
import memhub
import string
import stdio
import stdlib
import w32.win32file as win32file
import w32.win32base as win32base
import w32.fileio as fileio
import hashlib
import viperlib


# ============================================================
# IncludesScanner - includes 目录递归扫描
#
# 递归扫描 includes 目录，收集所有 .py 文件路径和 SHA1，
# 为 Phase1 stub 生成和 Phase2 stub 合并提供基础数据。
#
# 使用 Win32 FindFirstFileA/FindNextFileA 实现目录遍历。
# ============================================================

# 全局 mbuddy 指针
_mbuddy: memhub.MemManager | t.CPtr

# 文件路径最大长度
MAX_PATH_LEN: t.CDefine = 512
# 单次扫描最大文件数
MAX_FILES: t.CDefine = 256


# ============================================================
# FileEntry - 文件条目（路径 + SHA1）
# ============================================================
@t.NoVTable
class FileEntry:
    """扫描到的文件条目"""
    Path: str           # 文件绝对路径
    Sha1: str           # SHA1 前16字符（16字节+null）
    RelPath: str        # 相对于 includes 根目录的路径
    ModuleName: str     # 模块名（如 "ast.parser"）


# ============================================================
# ScanResult - 扫描结果
# ============================================================
@t.NoVTable
class ScanResult:
    """扫描结果集合"""
    Entries: FileEntry | t.CPtr   # FileEntry 数组
    Count: t.CInt
    Capacity: t.CInt


# ============================================================
# create_scan_result - 创建扫描结果容器
# ============================================================
def create_scan_result(pool: memhub.MemBuddy | t.CPtr) -> ScanResult | t.CPtr:
    """创建扫描结果容器，预分配 MAX_FILES 个槽位"""
    if pool is None:
        return None
    size: t.CSizeT = MAX_FILES * FileEntry.__sizeof__()
    # 使用 stdlib.malloc 避免 mbuddy 池耗尽（与 StubMerger 保持一致）
    entries: FileEntry | t.CPtr = stdlib.malloc(size)
    if entries is None:
        return None
    string.memset(entries, 0, size)

    result: ScanResult | t.CPtr = stdlib.malloc(ScanResult.__sizeof__())
    if result is None:
        return None
    string.memset(result, 0, ScanResult.__sizeof__())
    result.Entries = entries
    result.Count = 0
    result.Capacity = MAX_FILES
    return result


# ============================================================
# add_file_entry - 向扫描结果添加文件条目
# ============================================================
def add_file_entry(result: ScanResult | t.CPtr,
                   pool: memhub.MemBuddy | t.CPtr,
                   abs_path: str, rel_path: str,
                   sha1: str) -> int:
    """添加文件条目到扫描结果，返回 0 成功"""
    if result is None or pool is None:
        return 1
    if result.Count >= result.Capacity:
        return 1

    # 计算模块名：rel_path 中的 / 替换为 .，去掉 .py 扩展名
    rel_len: t.CSizeT = string.strlen(rel_path)
    mod_buf: str = stdlib.malloc(rel_len + 1)
    if mod_buf is None:
        return 1
    string.strcpy(mod_buf, rel_path)
    # 替换 / 为 .
    for i in range(rel_len):
        ch: t.CChar = mod_buf[i]
        if ch == '/' or ch == '\\':
            mod_buf[i] = '.'
    # 去掉 .py 扩展名
    if rel_len >= 3:
        if mod_buf[rel_len - 3] == '.' and mod_buf[rel_len - 2] == 'p' and mod_buf[rel_len - 1] == 'y':
            mod_buf[rel_len - 3] = '\0'

    # 获取条目地址
    entry_size: t.CSizeT = FileEntry.__sizeof__()
    entry_addr: t.CUInt64T = t.CUInt64T(result.Entries) + result.Count * entry_size
    entry: FileEntry | t.CPtr = (FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
    if entry is None:
        return 1

    # 复制路径字符串
    abs_len: t.CSizeT = string.strlen(abs_path)
    abs_buf: str = stdlib.malloc(abs_len + 1)
    if abs_buf is None:
        return 1
    string.strcpy(abs_buf, abs_path)
    entry.Path = abs_buf

    # 复制 SHA1
    sha1_buf: str = stdlib.malloc(17)
    if sha1_buf is None:
        return 1
    string.strcpy(sha1_buf, sha1)
    entry.Sha1 = sha1_buf

    # 复制相对路径
    rel_buf: str = stdlib.malloc(rel_len + 1)
    if rel_buf is None:
        return 1
    string.strcpy(rel_buf, rel_path)
    entry.RelPath = rel_buf

    # 复制模块名
    entry.ModuleName = mod_buf

    result.Count += 1
    return 0


# ============================================================
# compute_file_sha1 - 读取文件内容并计算 SHA1
#
# 与 Projectrans.py 一致：CRLF → LF 转换后计算 SHA1。
# Projectrans.py 用 Python 文本模式读取（自动 CRLF→LF），
# IncludesScanner 用二进制模式读取，需手动去除 \r。
# ============================================================
def compute_file_sha1(pool: memhub.MemBuddy | t.CPtr,
                      file_path: str) -> str:
    """读取文件内容并计算 SHA1 前16字符（CRLF→LF 后计算，与 Projectrans.py 一致）"""
    if pool is None or file_path is None:
        return None

    # 打开文件
    f: fileio.File | t.CPtr = fileio.File(file_path, fileio.MODE.R)
    if f.closed:
        return None

    # 分配读取缓冲区（128KB，足够大多数 .py 文件）
    # 使用 stdlib.malloc 避免 mbuddy 池耗尽（每文件 128KB，60+ 文件会耗尽 16MB 池）
    BUF_SIZE: t.CSizeT = 131072
    buf: bytes = stdlib.malloc(BUF_SIZE)
    if buf is None:
        f.close()
        return None

    # 读取文件内容
    bytes_read: t.CInt64T = f.read_all(buf, BUF_SIZE)
    f.close()
    if bytes_read <= 0:
        return None

    # 原地去除 \r（CRLF → LF），与 Projectrans.py 文本模式读取一致
    write_pos: t.CSizeT = 0
    read_pos: t.CSizeT = 0
    while read_pos < bytes_read:
        ch: t.CChar = buf[read_pos]
        if ch != '\r':
            buf[write_pos] = ch
            write_pos += 1
        read_pos += 1

    # 添加 null 终止符
    buf[write_pos] = 0

    # 计算 SHA1
    ctx: hashlib.sha1 | t.CPtr = hashlib.sha1()
    if ctx is None:
        return None
    ctx.update(buf)

    digest: bytes = stdlib.malloc(hashlib.SHA1_DIGEST_LEN)
    if digest is None:
        return None
    ctx.final(digest)

    # 转为十六进制字符串（取前 8 字节 = 16 个十六进制字符）
    hex_buf: str = stdlib.malloc(17)
    if hex_buf is None:
        return None
    for i in range(8):
        hi: int = (digest[i] >> 4) & 0xF
        lo: int = digest[i] & 0xF
        if hi < 10:
            hex_buf[i * 2] = '0' + hi
        else:
            hex_buf[i * 2] = 'a' + (hi - 10)
        if lo < 10:
            hex_buf[i * 2 + 1] = '0' + lo
        else:
            hex_buf[i * 2 + 1] = 'a' + (lo - 10)
    hex_buf[16] = '\0'
    return hex_buf


# ============================================================
# scan_directory_recursive - 递归扫描目录
#
# 使用 FindFirstFileA/FindNextFileA 遍历目录树，
# 对每个 .py 文件计算 SHA1 并添加到结果中。
# ============================================================
def scan_directory_recursive(pool: memhub.MemBuddy | t.CPtr,
                             root_dir: str,
                             rel_prefix: str,
                             result: ScanResult | t.CPtr) -> int:
    """递归扫描目录，收集 .py 文件"""
    if pool is None or root_dir is None or result is None:
        return 1

    # 构造搜索模式: root_dir/*
    root_len: t.CSizeT = string.strlen(root_dir)
    pattern: bytes = stdlib.malloc(root_len + 4)
    if pattern is None:
        return 1
    viperlib.snprintf(pattern, root_len + 4, "%s/*", root_dir)

    # 使用 FindFirstFileA 开始搜索
    find_data: win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(win32file.WIN32_FIND_DATAA.__sizeof__())
    if find_data is None:
        return 1
    string.memset(find_data, 0, win32file.WIN32_FIND_DATAA.__sizeof__())

    handle: win32base.HANDLE = win32file.FindFirstFileA(pattern, find_data)
    if handle == win32base.INVALID_HANDLE_VALUE:
        return 1

    # 遍历所有文件和子目录
    while True:
        # 跳过 . 和 ..
        fname: str = find_data.cFileName
        if fname is not None:
            fname0: t.CChar = fname[0]
            if fname0 == '.':
                fname1: t.CChar = fname[1]
                if fname1 == '\0':
                    # "."
                    if win32file.FindNextFileA(handle, find_data) == 0:
                        break
                    continue
                elif fname1 == '.':
                    fname2: t.CChar = fname[2]
                    if fname2 == '\0':
                        # ".."
                        if win32file.FindNextFileA(handle, find_data) == 0:
                            break
                        continue

        # 检查是否为目录
        is_dir: int = find_data.dwFileAttributes & win32file.FILE_ATTRIBUTE_DIRECTORY

        # 构造完整路径
        fname_len: t.CSizeT = string.strlen(fname)
        full_path: bytes = stdlib.malloc(root_len + fname_len + 2)
        if full_path is None:
            break
        viperlib.snprintf(full_path, root_len + fname_len + 2, "%s/%s", root_dir, fname)

        # 构造相对路径
        prefix_len: t.CSizeT = 0
        if rel_prefix is not None:
            prefix_len = string.strlen(rel_prefix)
        rel_path: bytes = stdlib.malloc(prefix_len + fname_len + 2)
        if rel_path is None:
            break
        if rel_prefix is not None and prefix_len > 0:
            viperlib.snprintf(rel_path, prefix_len + fname_len + 2, "%s/%s", rel_prefix, fname)
        else:
            string.strcpy(rel_path, fname)

        if is_dir != 0:
            # 递归扫描子目录
            scan_directory_recursive(pool, full_path, rel_path, result)
        else:
            # 检查是否为 .py 文件
            is_py: int = 0
            if fname_len >= 3:
                if fname[fname_len - 3] == '.' and fname[fname_len - 2] == 'p' and fname[fname_len - 1] == 'y':
                    is_py = 1

            if is_py != 0:
                # 跳过 __pycache__ 目录下的文件
                is_pycache: int = 0
                if rel_prefix is not None:
                    if string.strcmp(rel_prefix, "__pycache__") == 0:
                        is_pycache = 1

                if is_pycache == 0:
                    # 计算 SHA1
                    sha1: str = compute_file_sha1(pool, full_path)
                    if sha1 is not None:
                        add_file_entry(result, pool, full_path, rel_path, sha1)
                        stdio.printf("  [scan] %s -> %s\n", rel_path, sha1)

        # 继续搜索下一个文件
        if win32file.FindNextFileA(handle, find_data) == 0:
            break

    win32file.FindClose(handle)
    return 0


# ============================================================
# scan_includes - 扫描 includes 目录入口
#
# 扫描指定的 includes 目录，返回所有 .py 文件的路径和 SHA1。
# ============================================================
def scan_includes(pool: memhub.MemBuddy | t.CPtr,
                  includes_dir: str) -> ScanResult | t.CPtr:
    """扫描 includes 目录，返回所有 .py 文件的扫描结果"""
    if pool is None or includes_dir is None:
        return None

    stdio.printf("[Phase1] 扫描 includes 目录: %s\n", includes_dir)

    result: ScanResult | t.CPtr = create_scan_result(pool)
    if result is None:
        return None

    scan_directory_recursive(pool, includes_dir, None, result)

    stdio.printf("[Phase1] 扫描完成: %d 个 .py 文件\n", result.Count)
    return result


# ============================================================
# find_entry_by_module - 按模块名查找文件条目
# ============================================================
def find_entry_by_module(result: ScanResult | t.CPtr,
                         module_name: str) -> FileEntry | t.CPtr:
    """按模块名查找文件条目"""
    if result is None or module_name is None:
        return None
    entry_size: t.CSizeT = FileEntry.__sizeof__()
    for i in range(result.Count):
        entry_addr: t.CUInt64T = t.CUInt64T(result.Entries) + i * entry_size
        entry: FileEntry | t.CPtr = (FileEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
        if entry is not None:
            if entry.ModuleName is not None:
                if entry.ModuleName == module_name:
                    return entry
    return None
