# retoor <retoor@molodetz.nl>
from __future__ import annotations

import re
import sys

from devplacepy.services.jobs.isslop.analysis.signals import (
    AXIS_QUALITY,
    SEVERITY_MEDIUM,
    FileContext,
    Signal,
)

PYTHON_IMPORT_PATTERN = re.compile(r"^\s*(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))", re.MULTILINE)
JS_IMPORT_PATTERN = re.compile(r"""(?:import[^'"]*from\s*|import\s*\(\s*|require\s*\(\s*)['"]([^'"]+)['"]""")

PYTHON_IMPORT_ALIASES: dict[str, str] = {
    "pil": "pillow",
    "cv2": "opencv-python",
    "yaml": "pyyaml",
    "bs4": "beautifulsoup4",
    "dotenv": "python-dotenv",
    "sklearn": "scikit-learn",
    "dateutil": "python-dateutil",
    "jose": "python-jose",
    "jwt": "pyjwt",
    "magic": "python-magic",
    "serial": "pyserial",
    "usb": "pyusb",
    "redis": "redis",
    "attr": "attrs",
    "google": "google",
    "pkg_resources": "setuptools",
    "setuptools": "setuptools",
}

NODE_BUILTINS: frozenset[str] = frozenset(
    {
        "assert", "async_hooks", "buffer", "child_process", "cluster", "console", "constants",
        "crypto", "dgram", "diagnostics_channel", "dns", "domain", "events", "fs", "http", "http2",
        "https", "inspector", "module", "net", "os", "path", "perf_hooks", "process", "punycode",
        "querystring", "readline", "repl", "sea", "sqlite", "stream", "string_decoder", "test",
        "timers", "tls", "trace_events", "tty", "url", "util", "v8", "vm", "wasi",
        "worker_threads", "zlib",
    }
)

NON_PACKAGE_PREFIXES: tuple[str, ...] = ("@/", "~", "#", "$")

MAX_FLAGS_PER_FILE: int = 5


def _normalize_package(name: str) -> str:
    return name.lower().replace("-", "_").replace(".", "_")


def _python_findings(context: FileContext) -> list[Signal]:
    repo = context.repo
    if repo is None or not repo.has_python_manifest:
        return []
    known = {_normalize_package(dep) for dep in repo.python_dependencies}
    local = {_normalize_package(module) for module in repo.local_python_modules}
    findings: list[Signal] = []
    seen: set[str] = set()
    for match in PYTHON_IMPORT_PATTERN.finditer(context.text):
        module = (match.group(1) or match.group(2) or "").split(".")[0]
        if not module or module in seen:
            continue
        seen.add(module)
        normalized = _normalize_package(module)
        aliased = _normalize_package(PYTHON_IMPORT_ALIASES.get(normalized, normalized))
        if normalized in sys.stdlib_module_names or module in sys.stdlib_module_names:
            continue
        if normalized in known or aliased in known or normalized in local:
            continue
        line = context.text.count("\n", 0, match.start()) + 1
        findings.append(
            Signal(
                "DEP_UNRESOLVED",
                f"Import '{module}' resolves to no manifest dependency, stdlib or local module",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                5.0,
                line,
                match.group(0).strip(),
            )
        )
    return findings[:MAX_FLAGS_PER_FILE]


def _javascript_findings(context: FileContext) -> list[Signal]:
    repo = context.repo
    if repo is None or not repo.has_javascript_manifest:
        return []
    known = {dep.lower() for dep in repo.javascript_dependencies}
    findings: list[Signal] = []
    seen: set[str] = set()
    aliases = tuple(repo.javascript_alias_prefixes)
    for match in JS_IMPORT_PATTERN.finditer(context.text):
        target = match.group(1)
        if target.startswith((".", "/", "http:", "https:")):
            continue
        if target.startswith(NON_PACKAGE_PREFIXES):
            continue
        if aliases and target.startswith(aliases):
            continue
        stripped = target.removeprefix("node:")
        parts = stripped.split("/")
        package = "/".join(parts[:2]) if stripped.startswith("@") and len(parts) >= 2 else parts[0]
        if package in seen:
            continue
        seen.add(package)
        if ":" in package:
            continue
        if package.lower() in NODE_BUILTINS or target.startswith("node:"):
            continue
        if package.lower() in known:
            continue
        line = context.text.count("\n", 0, match.start()) + 1
        findings.append(
            Signal(
                "DEP_UNRESOLVED",
                f"Import '{package}' is not declared in package.json",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                5.0,
                line,
                match.group(0).strip()[:120],
            )
        )
    return findings[:MAX_FLAGS_PER_FILE]


def detect_hallucination(context: FileContext) -> list[Signal]:
    if context.language == "python":
        return _python_findings(context)
    if context.language in ("javascript", "typescript"):
        return _javascript_findings(context)
    return []
