"""Probe module - codebase-aware pre-decomposition assessment.

This module provides the Socratic interrogation phase before decomposition.
It probes the codebase structure to ask context-aware questions rather than
generic ones.

The probe phase:
1. Scans codebase structure (glob patterns)
2. Detects existing patterns (frameworks, auth, db, etc.)
3. Generates context-aware questions based on findings
4. Refines the directive with user input
"""

from __future__ import annotations

import fnmatch
import logging
import os
from dataclasses import dataclass, field
from pathlib import Path

from cleave.core.assessment import PATTERNS, match_pattern
from cleave.core.performance import timed

logger = logging.getLogger(__name__)


# =============================================================================
# DATA CLASSES
# =============================================================================


@dataclass
class ProbeQuestion:
    """A context-aware question generated from codebase probing."""

    question: str
    options: list[str]
    trigger_type: str  # file_exists, file_missing, keyword_detected, always
    trigger_detail: str  # The pattern or keyword that triggered this
    source_pattern: str | None = None  # Which pattern library entry generated this


@dataclass
class ProbeResult:
    """Result of codebase probing."""

    detected_stack: dict[str, str | None] = field(default_factory=dict)
    relevant_files: list[str] = field(default_factory=list)
    triggered_questions: list[ProbeQuestion] = field(default_factory=list)
    pattern_match: str | None = None
    pattern_confidence: float = 0.0
    directive_keywords: list[str] = field(default_factory=list)


# =============================================================================
# STACK DETECTION
# =============================================================================

# Framework/technology detection patterns
STACK_DETECTORS = {
    "language": {
        "python": ["*.py", "pyproject.toml", "setup.py", "requirements.txt"],
        "typescript": ["*.ts", "*.tsx", "tsconfig.json"],
        "javascript": ["*.js", "*.jsx", "package.json"],
        "go": ["*.go", "go.mod"],
        "rust": ["*.rs", "Cargo.toml"],
    },
    "framework": {
        "fastapi": ["**/main.py", "**/app.py"],  # + check for fastapi import
        "flask": ["**/app.py", "**/wsgi.py"],
        "django": ["**/settings.py", "**/wsgi.py", "manage.py"],
        "express": ["**/app.js", "**/server.js"],
        "react": ["**/App.tsx", "**/App.jsx", "package.json"],
        "vue": ["**/App.vue", "vue.config.js"],
        "nextjs": ["next.config.js", "next.config.mjs"],
    },
    "database": {
        "postgres": ["**/models/**", "**/migrations/**"],
        "mongodb": ["**/models/**", "**/schemas/**"],
        "sqlite": ["*.db", "*.sqlite", "*.sqlite3"],
    },
    "auth": {
        "jwt": ["**/auth/**", "**/security/**"],
        "oauth": ["**/oauth/**", "**/providers/**"],
        "session": ["**/session/**"],
    },
    "infrastructure": {
        "docker": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml"],
        "kubernetes": ["**/k8s/**", "**/kubernetes/**", "*.yaml"],
        "terraform": ["*.tf", "**/terraform/**"],
    },
}


def detect_stack(root_dir: Path) -> dict[str, str | None]:
    """Detect the technology stack in a codebase.

    Args:
        root_dir: Root directory to scan

    Returns:
        Dict mapping category (language, framework, etc.) to detected value
    """
    detected = {}

    for category, technologies in STACK_DETECTORS.items():
        for tech, patterns in technologies.items():
            for pattern in patterns:
                # Use glob to check if pattern exists
                matches = list(root_dir.glob(pattern))
                if matches:
                    detected[category] = tech
                    break
            if category in detected:
                break
        if category not in detected:
            detected[category] = None

    return detected


def find_relevant_files(root_dir: Path, directive: str, max_files: int = 10) -> list[str]:
    """Find files relevant to the directive based on keywords.

    Args:
        root_dir: Root directory to scan
        directive: The user's directive
        max_files: Maximum number of files to return

    Returns:
        List of relative file paths
    """
    directive_lower = directive.lower()

    # Extract keywords from directive
    keywords = []
    keyword_candidates = [
        "auth",
        "user",
        "login",
        "api",
        "route",
        "model",
        "database",
        "db",
        "cache",
        "config",
        "test",
        "migration",
        "schema",
        "webhook",
        "payment",
        "stripe",
        "email",
        "notification",
    ]
    for kw in keyword_candidates:
        if kw in directive_lower:
            keywords.append(kw)

    # Also extract potential entity names (capitalized words)
    words = directive.split()
    for word in words:
        if word[0].isupper() and len(word) > 2:
            keywords.append(word.lower())

    if not keywords:
        keywords = ["main", "app", "index"]

    relevant = []

    # Walk the directory and score files
    for root, dirs, files in os.walk(root_dir):
        # Skip hidden directories and common non-source directories
        dirs[:] = [d for d in dirs if not d.startswith(".") and d not in ["node_modules", "__pycache__", "venv", ".venv", "dist", "build"]]

        rel_root = Path(root).relative_to(root_dir)

        for fname in files:
            if fname.startswith("."):
                continue

            # Check if any keyword appears in path or filename
            full_path = str(rel_root / fname).lower()
            for kw in keywords:
                if kw in full_path:
                    relevant.append(str(rel_root / fname))
                    break

    # Sort by relevance (prefer shorter paths, source files)
    def score_file(f: str) -> tuple[int, int]:
        depth = f.count("/")
        # Prefer source files
        ext_priority = 0
        if f.endswith((".py", ".ts", ".js", ".go", ".rs")):
            ext_priority = -1
        return (ext_priority, depth)

    relevant.sort(key=score_file)
    return relevant[:max_files]


# =============================================================================
# QUESTION GENERATION
# =============================================================================


def evaluate_trigger(trigger: dict, root_dir: Path, directive: str) -> tuple[bool, str]:
    """Evaluate whether a probe question trigger condition is met.

    Args:
        trigger: Trigger definition from pattern library
        root_dir: Root directory to check
        directive: User's directive text

    Returns:
        Tuple of (triggered: bool, detail: str)
    """
    trigger_type = trigger.get("type", "always")

    if trigger_type == "always":
        return True, "always"

    elif trigger_type == "file_exists":
        pattern = trigger.get("pattern", "")
        matches = list(root_dir.glob(pattern))
        if matches:
            return True, f"found: {matches[0]}"
        return False, ""

    elif trigger_type == "file_missing":
        pattern = trigger.get("pattern", "")
        matches = list(root_dir.glob(pattern))
        if not matches:
            return True, f"missing: {pattern}"
        return False, ""

    elif trigger_type == "keyword_detected":
        keywords = trigger.get("keywords", [])
        directive_lower = directive.lower()
        for kw in keywords:
            if kw in directive_lower:
                return True, f"keyword: {kw}"
        return False, ""

    return False, ""


def generate_probe_questions(
    directive: str,
    root_dir: Path,
    pattern_id: str | None = None,
) -> list[ProbeQuestion]:
    """Generate context-aware questions based on codebase probing.

    Args:
        directive: User's directive
        root_dir: Root directory to probe
        pattern_id: Optional pattern ID to focus questions

    Returns:
        List of triggered ProbeQuestions
    """
    questions = []

    # If pattern specified, only use that pattern's questions
    if pattern_id and pattern_id in PATTERNS:
        patterns_to_check = {pattern_id: PATTERNS[pattern_id]}
    else:
        # Check all patterns that might match
        match = match_pattern(directive)
        if match:
            patterns_to_check = {match.pattern_id: PATTERNS[match.pattern_id]}
        else:
            # No match - use generic questions from all patterns
            patterns_to_check = PATTERNS

    for pid, pattern in patterns_to_check.items():
        probe_qs = pattern.get("probe_questions", [])
        for pq in probe_qs:
            trigger = pq.get("trigger", {"type": "always"})
            triggered, detail = evaluate_trigger(trigger, root_dir, directive)

            if triggered:
                questions.append(
                    ProbeQuestion(
                        question=pq["question"],
                        options=pq.get("options", ["freeform"]),
                        trigger_type=trigger.get("type", "always"),
                        trigger_detail=detail,
                        source_pattern=pid,
                    )
                )

    return questions


# =============================================================================
# MAIN PROBE FUNCTION
# =============================================================================


@timed("probe_codebase")
def probe_codebase(directive: str, root_dir: str | Path = ".") -> ProbeResult:
    """Probe codebase to generate context-aware assessment.

    This is the main entry point for the Socratic interrogation phase.

    Args:
        directive: User's task directive
        root_dir: Root directory of the codebase (default: current dir)

    Returns:
        ProbeResult with detected stack, relevant files, and triggered questions
    """
    root = Path(root_dir).resolve()

    if not root.exists():
        logger.warning(f"Probe directory does not exist: {root}")
        return ProbeResult()

    # Step 1: Detect stack
    detected_stack = detect_stack(root)

    # Step 2: Find relevant files
    relevant_files = find_relevant_files(root, directive)

    # Step 3: Match pattern
    match = match_pattern(directive)
    pattern_match = match.name if match else None
    pattern_confidence = match.confidence if match else 0.0
    pattern_id = match.pattern_id if match else None

    # Step 4: Extract directive keywords for reference
    directive_lower = directive.lower()
    directive_keywords = []
    all_keywords = set()
    for pattern in PATTERNS.values():
        all_keywords.update(pattern.get("keywords", []))
    for kw in all_keywords:
        if kw in directive_lower:
            directive_keywords.append(kw)

    # Step 5: Generate context-aware questions
    questions = generate_probe_questions(directive, root, pattern_id)

    return ProbeResult(
        detected_stack=detected_stack,
        relevant_files=relevant_files,
        triggered_questions=questions,
        pattern_match=pattern_match,
        pattern_confidence=pattern_confidence,
        directive_keywords=directive_keywords,
    )


def format_probe_result(result: ProbeResult) -> dict:
    """Format ProbeResult for JSON/CLI output.

    Args:
        result: ProbeResult to format

    Returns:
        Dict suitable for JSON serialization
    """
    return {
        "detected_stack": result.detected_stack,
        "relevant_files": result.relevant_files,
        "pattern_match": result.pattern_match,
        "pattern_confidence": result.pattern_confidence,
        "directive_keywords": result.directive_keywords,
        "inferred_questions": [
            {
                "question": q.question,
                "options": q.options,
                "trigger": q.trigger_type,
                "trigger_detail": q.trigger_detail,
                "source_pattern": q.source_pattern,
            }
            for q in result.triggered_questions
        ],
    }
