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

import re

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

CODE_LANGUAGES_FOR_DEFAULTS: frozenset[str] = frozenset(
    {"python", "javascript", "typescript", "php", "go", "java", "ruby", "csharp", "yaml", "json", "html"}
)

DEFAULT_SECRET_PATTERN = re.compile(
    r"(secret|jwt[_-]?secret|secret[_-]?key|signing[_-]?key|session[_-]?secret)\w*\s*[:=]\s*"
    r"[\"'](your[_-]secret[_-]key(?:[_-]here)?|your[_-]super[_-]secret|supersecret\w*|super[_-]secret"
    r"|my[_-]?secret(?:[_-]?key)?|secret(?:123)?|secret[_-]key|change[_-]?(this|me)\w*|keyboard cat)[\"']",
    re.IGNORECASE,
)

WILDCARD_CORS_CREDENTIALS_PATTERN = re.compile(
    r"allow_origins\s*=\s*\[\s*[\"']\*[\"']\s*\][\s\S]{0,200}allow_credentials\s*=\s*True"
    r"|allow_credentials\s*=\s*True[\s\S]{0,200}allow_origins\s*=\s*\[\s*[\"']\*[\"']\s*\]"
    r"|credentials\s*:\s*true[\s\S]{0,120}origin\s*:\s*[\"']\*[\"']"
    r"|origin\s*:\s*[\"']\*[\"'][\s\S]{0,120}credentials\s*:\s*true",
    re.IGNORECASE,
)
WILDCARD_CORS_PATTERN = re.compile(
    r"allow_origins\s*=\s*\[\s*[\"']\*[\"']\s*\]"
    r"|Access-Control-Allow-Origin[\"']?\s*[,:]\s*[\"']\*[\"']"
    r"|app\.use\(\s*cors\(\s*\)\s*\)"
    r"|origin\s*:\s*[\"']\*[\"']",
    re.IGNORECASE,
)

DEBUG_ENABLED_PATTERN = re.compile(
    r"\.run\([^)]*debug\s*=\s*True|^\s*DEBUG\s*=\s*True\s*$|debug\s*:\s*true\s*,?\s*//|APP_DEBUG=true",
    re.IGNORECASE | re.MULTILINE,
)

SEED_CREDENTIAL_PATTERN = re.compile(
    r"[\"'](admin123|password123|test123|admin@example\.com|test@test\.com|admin@admin\.com|letmein|qwerty123)[\"']",
    re.IGNORECASE,
)

DEFAULT_DATABASE_PATTERN = re.compile(
    r"mongodb(\+srv)?://localhost(:27017)?/(mydb|myapp|test|mydatabase|app)"
    r"|(postgres(ql)?|mysql)://(user|root|admin|postgres):(password|pass|root|admin|secret)@localhost",
    re.IGNORECASE,
)

TUTORIAL_SERVER_PATTERN = re.compile(
    r"console\.log\(\s*[\"'`]Server (is )?(running|listening|started) (on|at) (port |http)",
    re.IGNORECASE,
)

APP_NAME_DEFAULT_PATTERN = re.compile(
    r"[\"'](my-?app|my-?project|test-?app|awesome-?project|my-?website)[\"']\s*[,:]",
    re.IGNORECASE,
)

MAX_FINDINGS: int = 10


def _line_of(text: str, position: int) -> int:
    return text.count("\n", 0, position) + 1


def detect_llm_defaults(context: FileContext) -> list[Signal]:
    if context.language not in CODE_LANGUAGES_FOR_DEFAULTS:
        return []
    findings: list[Signal] = []
    text = context.text

    for match in DEFAULT_SECRET_PATTERN.finditer(text):
        findings.append(
            Signal(
                "LLM_DEFAULT_SECRET",
                f"Stock LLM placeholder secret '{match.group(2)}' left in configuration",
                SEVERITY_STRONG,
                AXIS_QUALITY,
                5.0,
                _line_of(text, match.start()),
                match.group(0)[:120],
            )
        )

    credentials_combo = WILDCARD_CORS_CREDENTIALS_PATTERN.search(text)
    if credentials_combo:
        findings.append(
            Signal(
                "WILDCARD_CORS_CREDENTIALS",
                "Wildcard CORS origin combined with credentials, the canonical LLM scaffold vulnerability",
                SEVERITY_STRONG,
                AXIS_QUALITY,
                4.0,
                _line_of(text, credentials_combo.start()),
                credentials_combo.group(0)[:120],
            )
        )
    else:
        wildcard = WILDCARD_CORS_PATTERN.search(text)
        if wildcard:
            findings.append(
                Signal(
                    "WILDCARD_CORS",
                    "Wildcard CORS configuration, the default LLM scaffold",
                    SEVERITY_MEDIUM,
                    AXIS_QUALITY,
                    2.0,
                    _line_of(text, wildcard.start()),
                    wildcard.group(0)[:120],
                )
            )

    debug = DEBUG_ENABLED_PATTERN.search(text)
    if debug:
        findings.append(
            Signal(
                "DEBUG_ENABLED",
                "Debug mode committed enabled, the tutorial default",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                2.0,
                _line_of(text, debug.start()),
                debug.group(0).strip()[:120],
            )
        )

    for match in SEED_CREDENTIAL_PATTERN.finditer(text):
        findings.append(
            Signal(
                "SEED_CREDENTIALS",
                f"Stock demo credential {match.group(1)} in source",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                3.0,
                _line_of(text, match.start()),
                match.group(0)[:120],
            )
        )
        break

    database = DEFAULT_DATABASE_PATTERN.search(text)
    if database:
        findings.append(
            Signal(
                "DEFAULT_DATABASE_URL",
                "Tutorial-default database connection string",
                SEVERITY_MEDIUM,
                AXIS_QUALITY,
                1.0,
                _line_of(text, database.start()),
                database.group(0)[:120],
            )
        )

    tutorial = TUTORIAL_SERVER_PATTERN.search(text)
    if tutorial:
        findings.append(
            Signal(
                "TUTORIAL_SERVER_LOG",
                "Tutorial-style server startup console.log",
                SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                1.0,
                _line_of(text, tutorial.start()),
                tutorial.group(0)[:120],
            )
        )

    app_name = APP_NAME_DEFAULT_PATTERN.search(text)
    if app_name:
        findings.append(
            Signal(
                "DEFAULT_PROJECT_NAME",
                f"Default scaffold project name {app_name.group(1)}",
                SEVERITY_MEDIUM,
                AXIS_ORIGIN,
                1.0,
                _line_of(text, app_name.start()),
                app_name.group(0)[:80],
            )
        )

    return findings[:MAX_FINDINGS]
