from __future__ import annotations

import json
import re
from pathlib import Path
from typing import Any

from .constants import IMPLEMENTED_FOCAS_METHODS


def parse_fwlib_imports(fwlib_cs_path: str | Path) -> list[str]:
    text = Path(fwlib_cs_path).read_text(encoding="utf-8", errors="ignore")
    imports = re.findall(r"extern short\s+([A-Za-z0-9_]+)\s*\(", text)
    return sorted(set(imports))


def extract_dll_exports(dll_path: str | Path) -> list[str]:
    import pefile

    pe = pefile.PE(str(dll_path))
    exports = [entry.name.decode("ascii", "ignore") for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols if entry.name]
    return sorted(set(exports))


def _infer_metadata(dll_name: str) -> tuple[str, int, int]:
    lowered = dll_name.lower()
    if lowered == "fwlib64.dll":
        return ("generic-ethernet", 8, 1)
    if lowered == "fwlibe64.dll":
        return ("embedded-ethernet", 8, 1)
    if "30i" in lowered:
        return ("30i/31i/32i", 32, 2)
    if "ncg" in lowered:
        return ("ncguide-family", 32, 2)
    if "0id" in lowered:
        return ("0i-d-family", 24, 1)
    if "0dn" in lowered:
        return ("0-dn-family", 24, 1)
    return ("unknown", 8, 1)


def build_profile(dll_path: str | Path, fwlib_cs_path: str | Path) -> dict[str, Any]:
    dll_path = Path(dll_path)
    exports = extract_dll_exports(dll_path)
    wrapper_imports = set(parse_fwlib_imports(fwlib_cs_path))
    series_hint, max_axis_hint, max_path_hint = _infer_metadata(dll_path.name)
    export_set = set(exports)
    connection_methods = sorted(symbol for symbol in exports if symbol.startswith("cnc_allclibhndl"))
    mock_methods = sorted(set(IMPLEMENTED_FOCAS_METHODS) & export_set)
    wrapper_supported = sorted(wrapper_imports & export_set)
    return {
        "profile_name": dll_path.stem,
        "dll_name": dll_path.name,
        "series_hint": series_hint,
        "max_axis_hint": max_axis_hint,
        "max_path_hint": max_path_hint,
        "export_count": len(exports),
        "connection_methods": connection_methods,
        "mock_methods": mock_methods,
        "wrapper_supported_count": len(wrapper_supported),
        "wrapper_supported_methods": wrapper_supported,
        "exports": exports,
        "notes": [
            "Exports extracted directly from the 64-bit DLL PE export directory.",
            "Wrapper-supported methods are intersected with upstream fwlib.cs extern declarations.",
            "Axis and path hints are filename-family heuristics, not protocol-level proofs.",
        ],
    }


def write_profiles(dll_dir: str | Path, fwlib_cs_path: str | Path, out_dir: str | Path) -> list[Path]:
    dll_dir = Path(dll_dir)
    out_dir = Path(out_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    written: list[Path] = []
    for dll_path in sorted(dll_dir.glob("*.dll")):
        profile = build_profile(dll_path, fwlib_cs_path)
        out_path = out_dir / f"{dll_path.stem}.json"
        out_path.write_text(json.dumps(profile, indent=2), encoding="utf-8")
        written.append(out_path)
    return written

