"""
Intent router for Gremlin Chat.

Hybrid strategy:
- Fast keyword heuristics for clear intents
- Optional LLM fallback for ambiguous requests
- Smart default based on sheet context
"""

from __future__ import annotations

from enum import Enum
import re
from typing import Any, Dict, List, Optional, Tuple, TYPE_CHECKING

from pydantic import BaseModel, Field

if TYPE_CHECKING:  # Avoid runtime import cycles
    from ..api.v2.gremlin_chat import SheetContext

from .resolution_prep import DataType, ResolutionPrepRecommendation, detect_data_type


class GremlinMode(str, Enum):
    DATA_QUALITY = "data_quality"
    MODEL = "model"
    RESEARCH = "research"


class ResolutionTarget(str, Enum):
    SALESFORCE = "salesforce"  # CRM Truth - match against user's SFDC Accounts
    FOUNDRYGRAPH = "foundrygraph"  # World Truth - canonical names from FoundryGraph
    SMART = "smart"  # Waterfall: SFDC first, then FoundryGraph for non-matches


class ContextLevel(str, Enum):
    LIGHT = "light"  # headers + sheet dimensions + inferred types (no raw samples)
    MEDIUM = "medium"  # headers + inferred types + 10-row sample
    HEAVY = "heavy"  # headers + 50-row sample (full preview)


class IntentSignals(BaseModel):
    keywords_hit: list[str] = []
    requires_sample: bool = False
    multi_intent: bool = False
    suggested_context_level: ContextLevel = ContextLevel.HEAVY
    secondary_mode: Optional[GremlinMode] = None
    rationale: str = ""
    resolution_target: ResolutionTarget = ResolutionTarget.SMART
    resolution_target_set: bool = False
    resolution_target_explicit: bool = False
    salesforce_connected: bool = False
    requires_bulk_match: bool = False
    estimated_row_count: int = 0
    key_columns: list[str] = []
    key_column_types: Dict[str, str] = Field(default_factory=dict)
    key_columns_with_values: list[str] = []
    key_columns_missing_values: list[str] = []
    key_missing: bool = False
    template_target: Optional[str] = None
    template_confidence: float = 0.0


class IntentResult(BaseModel):
    mode: GremlinMode
    confidence: float  # 0.0 - 1.0
    signals: IntentSignals


DATA_QUALITY_KEYWORDS = [
    "fix",
    "correct",
    "standardize",
    "typo",
    "clean",
    "normalize",
    "dedupe",
    "duplicate",
]
MODEL_KEYWORDS = [
    "formula",
    "pivot",
    "calculate",
    "sum",
    "average",
    "sort",
    "chart",
    "vlookup",
    "if statement",
]
RESEARCH_KEYWORDS = [
    "look up",
    "enrich",
    "find info",
    "industry",
    "employees",
    "parent company",
    "headquarters",
    "revenue",
]
SFDC_KEYWORDS = ["salesforce", "sfdc", "crm", "account id", "match to", "in salesforce", "my accounts"]
CANONICAL_KEYWORDS = ["canonical", "standardize", "wikidata", "foundrygraph", "official name"]
DOMAIN_KEYWORDS = ["domain", "website", "url", "site"]
SFDC_ID_KEYWORDS = [
    "_fm_sfdc_accountid",
    "sfdc_accountid",
    "sfdc account id",
    "salesforce_accountid",
    "salesforce account id",
    "salesforce id",
    "account id",
    "accountid",
    "sfid",
    "crm id",
    "record id",
]
UID_KEYWORDS = ["company id", "organization id", "org id", "external id", "uid", "uuid", "guid"]

# Template routing patterns for model builders
TEMPLATE_PHRASE_PATTERNS = {
    "waterfall": [
        (r"\b(build|create|make)\b.*\bwaterfall\b", 0.95),
        (r"\bsales.*marketing.*funnel\b", 0.85),
        (r"\bconversion\s+funnel\b", 0.80),
        (r"\bwaterfall\b", 0.70),
    ],
    "capacity": [
        (r"\b(build|create)\b.*\bcapacity\b", 0.95),
        (r"\bae\s*(capacity|headcount)\b", 0.90),
        (r"\bsales\s*capacity\b", 0.90),
        (r"\bramp\s*(curve|model)\b", 0.85),
        (r"\b(enough|have)\b.*\baes?\b.*\bhit\b", 0.80),
    ],
    "coverage": [
        (r"\bpipeline\s*coverage\b", 0.95),
        (r"\bcoverage\s*(model|analysis)\b", 0.90),
        (r"\bwhen\b.*\bpipeline\b.*\b(created|built)\b", 0.85),
    ],
    "sdr": [
        (r"\bsdr\s*(efficiency|roi|model)\b", 0.95),
        (r"\bbdr\s*(efficiency|roi)\b", 0.90),
        (r"\boutbound\s*(roi|efficiency)\b", 0.85),
    ],
    "market_finder": [
        (r"\b(market|field)\s*finder\b", 0.95),
        (r"\bwhere\b.*\b(events?|marketing)\b", 0.80),
        (r"\bgeo(graphic)?\s*(analysis|scoring)\b", 0.85),
    ],
    "partner_overlap": [
        (r"\bpartner\s*overlap\b", 0.95),
        (r"\bco-?sell\b", 0.85),
        (r"\becosystem\s*(match|overlap)\b", 0.85),
    ],
    "territory_planner": [
        (r"\bterritory\s*(plan|assign)\b", 0.95),
        (r"\bsales\s*territory\b", 0.85),
    ],
}


def detect_template(message: str) -> Tuple[Optional[str], float]:
    """Return (template_type, confidence) if a model template should be opened."""
    message_lower = (message or "").lower()
    best_match: Tuple[Optional[str], float] = (None, 0.0)
    for template, patterns in TEMPLATE_PHRASE_PATTERNS.items():
        for pattern, confidence in patterns:
            if re.search(pattern, message_lower, re.IGNORECASE):
                if confidence > best_match[1]:
                    best_match = (template, confidence)
    if best_match[1] >= 0.7:
        return best_match
    return (None, 0.0)

# Enrichment tools that benefit from resolution prep
ENRICHMENT_TOOLS = {
    "normalize_company",
    "enrich_company_name",
    "enrich_company_firmographics",
    "enrich_contact",
    "match_salesforce_account",
    "company_to_domain",
    "enrich_from_foundrygraph",
}


def _sniff_key_columns(context: "SheetContext | None") -> Dict[str, Any]:
    """Detect canonical key columns (domain/UID/SFDC ID) from headers and samples."""
    headers = [str(h or "").strip() for h in getattr(context, "headers", []) or []]
    lower_headers = [h.lower() for h in headers]
    key_columns: list[str] = []
    key_column_types: Dict[str, str] = {}

    def _add_key(idx: int, key_type: str) -> None:
        header = headers[idx]
        if not header or header in key_column_types:
            return
        key_columns.append(header)
        key_column_types[header] = key_type

    # Header-based detection
    for idx, header in enumerate(lower_headers):
        if any(kw in header for kw in DOMAIN_KEYWORDS):
            _add_key(idx, "domain")
            continue
        if any(kw in header for kw in SFDC_ID_KEYWORDS):
            _add_key(idx, "sfdc_id")
            continue
        if any(kw in header for kw in UID_KEYWORDS):
            _add_key(idx, "uid")

    # Type-based detection (URL columns often imply domains)
    column_types = getattr(context, "column_types", {}) or {}
    for header, col_type in column_types.items():
        if col_type == "url" and header in headers:
            idx = headers.index(header)
            _add_key(idx, "domain")

    key_columns_with_values: list[str] = []
    key_columns_missing_values: list[str] = []
    sample_rows = getattr(context, "sample_rows", []) or []
    if key_columns:
        header_index = {headers[i]: i for i in range(len(headers))}
        for header in key_columns:
            idx = header_index.get(header)
            if idx is None:
                continue
            has_value = False
            for row in sample_rows:
                if idx < len(row):
                    cell = row[idx]
                    if str(cell or "").strip():
                        has_value = True
                        break
            if has_value:
                key_columns_with_values.append(header)
            else:
                key_columns_missing_values.append(header)

    if not key_columns:
        key_missing = True
    elif sample_rows:
        key_missing = not key_columns_with_values
    else:
        key_missing = False
    return {
        "key_columns": key_columns,
        "key_column_types": key_column_types,
        "key_columns_with_values": key_columns_with_values,
        "key_columns_missing_values": key_columns_missing_values,
        "key_missing": key_missing,
    }


def sniff_key_columns(context: "SheetContext | None") -> Dict[str, Any]:
    """Public helper to expose key sniffing outside the router."""
    return _sniff_key_columns(context)


def should_use_bulk_match(row_count: int, threshold: int = 2000) -> bool:
    """Determine if SFDC bulk matcher should be used."""
    try:
        return int(row_count) >= threshold
    except Exception:
        return False


async def classify_intent(
    message: str,
    context: "SheetContext | None",
    use_llm_fallback: bool = True,
) -> IntentResult:
    """
    Classify a user message into a Gremlin mode.

    Strategy:
    1. Fast path keyword detection
    2. Multi-intent handling
    3. Optional LLM fallback
    4. Smart default from sheet shape
    """
    message_lower = (message or "").lower()
    sfdc_connected = bool(getattr(context, "salesforce_connected", False))
    estimated_row_count = int(getattr(context, "row_count", 0) or 0)
    bulk_needed = should_use_bulk_match(estimated_row_count)
    resolution_target, resolution_target_explicit = _detect_resolution_target(
        message, context
    )
    key_signals = _sniff_key_columns(context)
    template_target, template_confidence = detect_template(message)

    if template_target:
        return IntentResult(
            mode=GremlinMode.MODEL,
            confidence=template_confidence,
            signals=IntentSignals(
                keywords_hit=[],
                requires_sample=False,
                suggested_context_level=ContextLevel.LIGHT,
                resolution_target=resolution_target,
                resolution_target_set=resolution_target_explicit,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=sfdc_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                template_target=template_target,
                template_confidence=template_confidence,
                **key_signals,
            ),
        )

    dq_hits = [kw for kw in DATA_QUALITY_KEYWORDS if kw in message_lower]
    model_hits = [kw for kw in MODEL_KEYWORDS if kw in message_lower]
    research_hits = [kw for kw in RESEARCH_KEYWORDS if kw in message_lower]

    has_multi_intent = (
        (dq_hits and model_hits)
        or (dq_hits and research_hits)
        or (model_hits and research_hits)
    )

    if model_hits and not dq_hits and not research_hits:
        needs_types = any(kw in message_lower for kw in ["date", "parse", "extract", "format"])
        return IntentResult(
            mode=GremlinMode.MODEL,
            confidence=0.9,
            signals=IntentSignals(
                keywords_hit=model_hits,
                requires_sample=needs_types,
                suggested_context_level=ContextLevel.MEDIUM if needs_types else ContextLevel.LIGHT,
                resolution_target=resolution_target,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=sfdc_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                **key_signals,
            ),
        )

    if research_hits and not model_hits:
        return IntentResult(
            mode=GremlinMode.RESEARCH,
            confidence=0.85,
            signals=IntentSignals(
                keywords_hit=research_hits,
                requires_sample=True,
                suggested_context_level=ContextLevel.HEAVY,
                resolution_target=resolution_target,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=sfdc_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                **key_signals,
            ),
        )

    if dq_hits and not model_hits and not research_hits:
        return IntentResult(
            mode=GremlinMode.DATA_QUALITY,
            confidence=0.9,
            signals=IntentSignals(
                keywords_hit=dq_hits,
                requires_sample=True,
                suggested_context_level=ContextLevel.HEAVY,
                resolution_target=resolution_target,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=sfdc_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                **key_signals,
            ),
        )

    if has_multi_intent:
        primary, secondary = _determine_intent_order(dq_hits, model_hits, research_hits)
        return IntentResult(
            mode=primary,
            confidence=0.7,
            signals=IntentSignals(
                keywords_hit=dq_hits + model_hits + research_hits,
                multi_intent=True,
                secondary_mode=secondary,
                suggested_context_level=ContextLevel.HEAVY,
                rationale="Multi-intent detected. Execute primary mode first, then secondary.",
                resolution_target=resolution_target,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=sfdc_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                **key_signals,
            ),
        )

    if use_llm_fallback:
        llm_result = await _llm_router_fallback(message, context)
        if llm_result is not None:
            llm_result.signals = llm_result.signals.copy(
                update={
                    "resolution_target": resolution_target,
                    "resolution_target_explicit": resolution_target_explicit,
                    "salesforce_connected": sfdc_connected,
                    "requires_bulk_match": bulk_needed,
                    "estimated_row_count": estimated_row_count,
                    **key_signals,
                }
            )
            return llm_result

    return _smart_default(
        context,
        resolution_target=resolution_target,
        resolution_target_explicit=resolution_target_explicit,
        salesforce_connected=sfdc_connected,
        bulk_needed=bulk_needed,
        estimated_row_count=estimated_row_count,
        key_signals=key_signals,
    )


def _determine_intent_order(
    dq_hits: list[str],
    model_hits: list[str],
    research_hits: list[str],
) -> Tuple[GremlinMode, Optional[GremlinMode]]:
    """
    For multi-intent, determine which mode to execute first.

    Order preference:
    1. DATA_QUALITY/RESEARCH first (prepare the data)
    2. MODEL second (analyze/visualize the prepared data)
    """
    if model_hits and (dq_hits or research_hits):
        primary = GremlinMode.DATA_QUALITY if dq_hits else GremlinMode.RESEARCH
        return primary, GremlinMode.MODEL

    if dq_hits and research_hits:
        return GremlinMode.DATA_QUALITY, GremlinMode.RESEARCH

    return GremlinMode.DATA_QUALITY, None


def _smart_default(
    context: "SheetContext | None",
    resolution_target: ResolutionTarget = ResolutionTarget.SMART,
    resolution_target_explicit: bool = False,
    salesforce_connected: bool = False,
    bulk_needed: bool = False,
    estimated_row_count: int = 0,
    key_signals: Optional[Dict[str, Any]] = None,
) -> IntentResult:
    """
    Smart fallback based on sheet characteristics.

    - Mostly text columns + vague message → DATA_QUALITY
    - Numeric columns + vague message → MODEL
    - Company/domain columns → RESEARCH
    """
    if not context or not getattr(context, "headers", None):
        return IntentResult(
            mode=GremlinMode.DATA_QUALITY,
            confidence=0.5,
            signals=IntentSignals(
                rationale="No context available, defaulting to DATA_QUALITY",
                resolution_target=resolution_target,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=salesforce_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                **(key_signals or {}),
            ),
        )

    headers_lower = [str(h).lower() for h in context.headers]
    enrichment_signals = ["company", "domain", "website", "url", "business", "organization"]
    if any(signal in " ".join(headers_lower) for signal in enrichment_signals):
        return IntentResult(
            mode=GremlinMode.DATA_QUALITY,
            confidence=0.6,
            signals=IntentSignals(
                rationale="Sheet contains company/domain columns. Defaulting to DATA_QUALITY; consider RESEARCH if enrichment requested.",
                resolution_target=resolution_target,
                resolution_target_explicit=resolution_target_explicit,
                salesforce_connected=salesforce_connected,
                requires_bulk_match=bulk_needed,
                estimated_row_count=estimated_row_count,
                **(key_signals or {}),
            ),
        )

    return IntentResult(
        mode=GremlinMode.DATA_QUALITY,
        confidence=0.5,
        signals=IntentSignals(
            rationale="Generic sheet, defaulting to DATA_QUALITY",
            resolution_target=resolution_target,
            resolution_target_explicit=resolution_target_explicit,
            salesforce_connected=salesforce_connected,
            requires_bulk_match=bulk_needed,
            estimated_row_count=estimated_row_count,
            **(key_signals or {}),
        ),
    )


def _detect_resolution_target(
    message: str,
    context: "SheetContext | None",
) -> Tuple[ResolutionTarget, bool]:
    """
    Detect which resolution target the user wants.

    Priority:
    1. Explicit user keywords ("use Salesforce", "canonical names")
    2. Sheet column signals (Account Id, SFDC columns → SFDC)
    3. Smart default if SFDC connected
    4. FoundryGraph if SFDC not connected
    """
    message_lower = (message or "").lower()

    if any(kw in message_lower for kw in SFDC_KEYWORDS):
        return ResolutionTarget.SALESFORCE, True

    if any(kw in message_lower for kw in CANONICAL_KEYWORDS):
        return ResolutionTarget.FOUNDRYGRAPH, True

    sfdc_connected = bool(getattr(context, "salesforce_connected", False))
    if not sfdc_connected:
        return ResolutionTarget.FOUNDRYGRAPH, False

    headers_lower = [str(h).lower() for h in getattr(context, "headers", []) or []]
    sfdc_column_signals = ["account id", "sfdc", "salesforce", "owner", "account name"]
    has_sfdc_columns = any(
        any(signal in header for signal in sfdc_column_signals) for header in headers_lower
    )
    if has_sfdc_columns:
        return ResolutionTarget.SALESFORCE, False

    return ResolutionTarget.SMART, False


async def _llm_router_fallback(message: str, context: "SheetContext | None") -> IntentResult | None:
    """
    Use a small/fast LLM call to classify ambiguous intent.
    Currently stubbed - returns None to fall through to smart default.

    TODO: Wire to Gemini Flash for ambiguous cases like:
    - "Can you clean this and add a pivot?" (multi-intent)
    - "Calculate parent company coverage %" (sounds MODEL but is RESEARCH)
    """
    # Stub: return None to use smart default
    # When implemented, call a fast model with a router prompt
    return None


async def check_resolution_prep_needed(
    intent: str | DataType | None,
    tool_name: str,
    context: "SheetContext | Any",
) -> Optional[ResolutionPrepRecommendation]:
    """
    Check if resolution prep should be suggested before executing an enrichment tool.

    Returns ResolutionPrepRecommendation if prep is needed, None otherwise.
    """
    # Skip if already offered in this session
    if getattr(context, "resolution_prep_offered", False):
        return None

    # Skip if tool doesn't need prep
    if tool_name not in ENRICHMENT_TOOLS:
        return None

    columns: List[str] = []
    sample_rows = None

    if hasattr(context, "sheet_columns") and getattr(context, "sheet_columns"):
        columns = list(getattr(context, "sheet_columns") or [])
    elif hasattr(context, "headers") and getattr(context, "headers"):
        columns = list(getattr(context, "headers") or [])
    elif hasattr(context, "columns") and getattr(context, "columns"):
        columns = list(getattr(context, "columns") or [])

    if hasattr(context, "data_rows") and getattr(context, "data_rows"):
        sample_rows = list(getattr(context, "data_rows") or [])[:10]
    elif hasattr(context, "sample_rows") and getattr(context, "sample_rows"):
        sample_rows = list(getattr(context, "sample_rows") or [])[:10]

    if not columns:
        return None

    recommendation = detect_data_type(columns, sample_rows)

    # Only suggest if prep is actually needed
    if not recommendation.prep_needed:
        return None

    # Mark that we've offered (don't nag)
    try:
        setattr(context, "resolution_prep_offered", True)
    except Exception:
        try:
            context.__dict__["resolution_prep_offered"] = True
        except Exception:
            pass

    return recommendation
