"""TDD recommendation heuristic — classifies tasks as TDD-friendly.

Pure function, no LLM calls. Runs on enriched task context after
the decomposer's enrichment step.
"""

from __future__ import annotations

import re

# ---------------------------------------------------------------------------
# TDD instruction block injected into task context.instructions
# ---------------------------------------------------------------------------

TDD_INSTRUCTION_BLOCK = (
    "## TDD Protocol\n"
    "This task is flagged for test-driven development.\n"
    "1. Write the test file FIRST with tests that define the expected behavior.\n"
    "2. Run the tests and confirm they FAIL (RED phase).\n"
    "3. Write the minimum implementation to make the tests pass (GREEN phase).\n"
    "4. Refactor if needed while keeping tests green.\n"
    "Do NOT write implementation code before you have a failing test."
)

# ---------------------------------------------------------------------------
# Signal definitions
# ---------------------------------------------------------------------------

# Primary signals — any one of these is strong enough to recommend TDD alone
_PRIMARY_PATTERNS: list[tuple[str, re.Pattern]] = [
    ("store_crud", re.compile(r"\b(create|read|update|delete|upsert|insert|crud|store|query|fetch|list_\w+|get_\w+)\b", re.I)),
    ("validator", re.compile(r"\b(valid|validation|validate|check|guard|constraint)\b", re.I)),
    ("parser", re.compile(r"\b(pars[ei]|parser|deserializ|serializ|decode|encode|transform|convert)\b", re.I)),
    ("resolver", re.compile(r"\b(resolv\w*|resolution|lookup|find_\w+|dispatch)\b", re.I)),
    ("state_transition", re.compile(r"\b(transition|state.?machine|status.?change|lifecycle|→|->)\b", re.I)),
    ("computation", re.compile(r"\b(calculat\w*|comput\w*|score|rank|sort|filter|aggregat\w*|algorithm|heuristic)\b", re.I)),
]

# Secondary signals — need 2+ (or 1 + primary) to recommend TDD
_SECONDARY_PATTERNS: list[tuple[str, re.Pattern]] = [
    ("domain_model", re.compile(r"\b(enum|model|field|dataclass|pydantic)\b", re.I)),
    ("test_mentioned", re.compile(r"\b(test|spec|assert|fixture|mock|unit.?test|integration.?test)\b", re.I)),
]

# Title/description/file patterns that suggest TDD is overhead
_NEGATIVE_PATTERNS: list[tuple[str, re.Pattern]] = [
    ("documentation", re.compile(r"\b(readme|docstring|comment|document|docs|changelog|release.?note)\b", re.I)),
    ("ui_template", re.compile(r"\b(template|html|css|dashboard|layout|ui|frontend|page)\b", re.I)),
    ("config_tweak", re.compile(r"\b(config|\.ya?ml|\.toml|\.ini|environment|env.?var|setting|timeout|default)\b", re.I)),
    ("deployment", re.compile(r"\b(docker|deploy|ci/?cd|pipeline|health.?check|infra|kubernetes|helm)\b", re.I)),
    ("migration_sql", re.compile(r"\b(migration|alter.?table|sql|schema.?change|column)\b", re.I)),
    ("mcp_wiring", re.compile(r"\b(mcp.?tool|thin.?wrapper|wiring|glue.?code|tools\.py)\b", re.I)),
]

# File path patterns that strongly signal one way or another
_TDD_FRIENDLY_PATHS = re.compile(r"(store|cache|validator|parser|resolver|task|model|deps|events)\.\w+$", re.I)
_TDD_UNFRIENDLY_PATHS = re.compile(r"(template|\.html|\.css|\.sql|\.ya?ml|docker|tools\.py|README|\.md)$", re.I)

# Complexity levels where TDD is most valuable (not trivial, not huge)
_TDD_SWEET_SPOT_COMPLEXITY = {"S", "M", "L"}

# Minimum positive signals needed to recommend TDD
_MIN_POSITIVE_SIGNALS = 2


def classify_tdd_recommendation(
    title: str,
    context: dict | None = None,
) -> dict:
    """Classify whether a task would benefit from TDD.

    Returns a dict with:
    - tdd_recommended: bool
    - reason: str explaining the classification
    - signals: list of signal names that fired
    """
    ctx = context or {}
    positive_signals: list[str] = []
    negative_signals: list[str] = []

    # Combine searchable text
    description = ctx.get("description", "") or ""
    testing = ctx.get("testing", "") or ""
    text = f"{title} {description} {testing}"
    files = ctx.get("files") or []

    # No text to analyze → can't recommend
    if not text.strip() and not files:
        return {"tdd_recommended": False, "reason": "Insufficient context to classify", "signals": []}

    # Check primary patterns (strong TDD indicators)
    has_primary = False
    for signal_name, pattern in _PRIMARY_PATTERNS:
        if pattern.search(text):
            positive_signals.append(signal_name)
            has_primary = True

    # Check secondary patterns
    for signal_name, pattern in _SECONDARY_PATTERNS:
        if pattern.search(text):
            positive_signals.append(signal_name)

    # Check negative patterns against text
    for signal_name, pattern in _NEGATIVE_PATTERNS:
        if pattern.search(text):
            negative_signals.append(signal_name)

    # Check file paths
    for f in files:
        if _TDD_FRIENDLY_PATHS.search(f):
            positive_signals.append(f"file:{f}")
        if _TDD_UNFRIENDLY_PATHS.search(f):
            negative_signals.append(f"file:{f}")

    # Complexity sweet spot
    complexity = ctx.get("complexity", "")
    if complexity in _TDD_SWEET_SPOT_COMPLEXITY:
        positive_signals.append(f"complexity:{complexity}")
    elif complexity == "XS":
        negative_signals.append("complexity:XS")

    # Decision: primary signal alone is enough; secondary needs 2+
    # Negative signals can still override
    net = len(positive_signals) - len(negative_signals)
    if net <= 0:
        recommended = False
    elif has_primary:
        recommended = True
    else:
        recommended = len(positive_signals) >= _MIN_POSITIVE_SIGNALS

    # Build reason
    if recommended:
        reason = f"TDD recommended: {', '.join(positive_signals[:3])}"
    elif not positive_signals:
        reason = "No TDD-friendly signals detected"
    elif negative_signals:
        reason = f"TDD skipped: {', '.join(negative_signals[:3])} outweigh signals"
    else:
        reason = "Insufficient TDD signals (need 2+)"

    return {
        "tdd_recommended": recommended,
        "reason": reason,
        "signals": positive_signals if recommended else negative_signals or ["none"],
    }
