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

import re

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

BARE_EXCEPT_PATTERN = re.compile(r"^\s*except\s*:\s*(#.*)?$")
BROAD_EXCEPT_PATTERN = re.compile(r"^\s*except\s+(?:Exception|BaseException)\s*(?:as\s+\w+)?\s*:\s*(#.*)?$")
PASS_PATTERN = re.compile(r"^\s*pass\s*(#.*)?$")
EMPTY_CATCH_PATTERN = re.compile(r"catch\s*(\([^)]*\))?\s*\{\s*\}")
LOG_ONLY_CATCH_PATTERN = re.compile(r"catch\s*\(\s*(\w+)\s*\)\s*\{\s*console\.(log|error)\([^)]*\);?\s*\}")
DEBUGGER_PATTERN = re.compile(r"^\s*debugger\s*;?\s*$")
TODO_REMOVE_PATTERN = re.compile(r"TODO:? remove", re.IGNORECASE)
CONSOLE_LOG_PATTERN = re.compile(r"\bconsole\.log\s*\(")
PRINT_PATTERN = re.compile(r"^\s*print\s*\(")

CONSOLE_LOG_THRESHOLD: int = 6
PRINT_DENSITY_THRESHOLD: float = 0.06
PRINT_MIN_SLOC: int = 60


def _python_swallowed(context: FileContext) -> list[Signal]:
    findings: list[Signal] = []
    for index, line in enumerate(context.lines):
        bare = BARE_EXCEPT_PATTERN.match(line)
        broad = BROAD_EXCEPT_PATTERN.match(line)
        if not bare and not broad:
            continue
        follow = ""
        for candidate in context.lines[index + 1: index + 3]:
            if candidate.strip():
                follow = candidate
                break
        swallows = bool(PASS_PATTERN.match(follow))
        if bare:
            findings.append(
                Signal(
                    "SWALLOWED_ERROR" if swallows else "BARE_EXCEPT",
                    "Bare except swallowing all errors" if swallows else "Bare except clause",
                    SEVERITY_STRONG,
                    AXIS_QUALITY,
                    3.0,
                    index + 1,
                    line.strip(),
                )
            )
        elif swallows:
            findings.append(
                Signal(
                    "SWALLOWED_ERROR",
                    "except Exception: pass swallows all errors",
                    SEVERITY_STRONG,
                    AXIS_QUALITY,
                    3.0,
                    index + 1,
                    f"{line.strip()} / pass",
                )
            )
    return findings


def detect_error_handling(context: FileContext) -> list[Signal]:
    findings: list[Signal] = []
    if context.language == "python":
        findings.extend(_python_swallowed(context))
        metrics = context.metrics
        if metrics is not None and metrics.sloc >= PRINT_MIN_SLOC:
            prints = sum(1 for line in context.lines if PRINT_PATTERN.match(line))
            if prints / max(metrics.sloc, 1) > PRINT_DENSITY_THRESHOLD:
                findings.append(
                    Signal(
                        "DEBUG_LEFTOVERS",
                        f"{prints} print() calls scattered through module",
                        SEVERITY_MEDIUM,
                        AXIS_QUALITY,
                        2.0,
                        1,
                        f"{prints} print calls in {metrics.sloc} SLOC",
                    )
                )
    if context.language in ("javascript", "typescript", "java", "php", "csharp"):
        for match in EMPTY_CATCH_PATTERN.finditer(context.text):
            line = context.text.count("\n", 0, match.start()) + 1
            findings.append(
                Signal(
                    "SWALLOWED_ERROR",
                    "Empty catch block swallows all errors",
                    SEVERITY_STRONG,
                    AXIS_QUALITY,
                    3.0,
                    line,
                    match.group(0)[:120],
                )
            )
        for match in LOG_ONLY_CATCH_PATTERN.finditer(context.text):
            line = context.text.count("\n", 0, match.start()) + 1
            findings.append(
                Signal(
                    "LOG_ONLY_CATCH",
                    "Catch block only logs to console",
                    SEVERITY_MEDIUM,
                    AXIS_QUALITY,
                    3.0,
                    line,
                    match.group(0)[:120],
                )
            )
        console_hits = len(CONSOLE_LOG_PATTERN.findall(context.text))
        if console_hits >= CONSOLE_LOG_THRESHOLD and "test" not in context.relative.lower():
            findings.append(
                Signal(
                    "DEBUG_LEFTOVERS",
                    f"{console_hits} console.log calls left in source",
                    SEVERITY_MEDIUM,
                    AXIS_QUALITY,
                    2.0,
                    1,
                    f"{console_hits} console.log calls",
                )
            )
        for number, line in enumerate(context.lines, start=1):
            if DEBUGGER_PATTERN.match(line):
                findings.append(
                    Signal("DEBUG_LEFTOVERS", "debugger statement left in source", SEVERITY_MEDIUM, AXIS_QUALITY, 2.0, number, line.strip())
                )
    for number, comment in context.comments:
        if TODO_REMOVE_PATTERN.search(comment):
            findings.append(
                Signal("DEBUG_LEFTOVERS", "TODO remove marker left in source", SEVERITY_MEDIUM, AXIS_QUALITY, 2.0, number, comment)
            )
    return findings
