"""Desktop-safe LLM-first Avatar intent classification (regex fallback when LLM unavailable)."""

from __future__ import annotations

import json
import logging
import re
from dataclasses import dataclass, field
from enum import Enum
from typing import Any

from iuve_connect_agents.desktop.ui.avatar_intent_router import (
    CompoundIntentResult,
    classify_avatar_request_type,
    detect_compound_intent,
)
from iuve_connect_agents.shared.feature_flags import (
    avatar_compound_intent_defer,
    avatar_execution_plan_enabled,
    avatar_llm_intent_enabled,
    avatar_llm_intent_first_enabled,
    avatar_local_ollama_fallback_enabled,
    avatar_local_ollama_reachable,
)

logger = logging.getLogger(__name__)

_VALID_INTENT_TYPES = frozenset(
    {"advisory", "device_action", "compound_plan", "clarify", "unknown", "plan_only"}
)

_CONFIDENCE_CLARIFY_THRESHOLD = 0.55


class AvatarIntentType(str, Enum):
    ADVISORY = "advisory"
    DEVICE_ACTION = "device_action"
    COMPOUND_PLAN = "compound_plan"
    PLAN_ONLY = "plan_only"
    CLARIFY = "clarify"
    UNKNOWN = "unknown"


@dataclass(frozen=True)
class AvatarIntentBridgeResult:
    request_type: str
    compound: CompoundIntentResult
    llm_intent: str | None = None
    source: str = "regex"


@dataclass(frozen=True)
class AvatarLlmIntentFirstResult:
    intent_type: AvatarIntentType
    confidence: float
    action: str = ""
    params: dict[str, Any] = field(default_factory=dict)
    advisory_answer: str | None = None
    clarify_question: str | None = None
    is_compound: bool = False
    source: str = "regex"
    explainability: dict[str, Any] = field(default_factory=dict)


def classify_avatar_intent_llm(
    text: str,
    *,
    context: dict[str, Any] | None = None,
    trust_state: str = "",
) -> AvatarLlmIntentFirstResult:
    """Primary LLM intent classifier; regex heuristics only when LLM is unavailable."""
    ctx = dict(context or {})
    trust = str(ctx.get("trust_state") or trust_state or "").strip()

    request_type = classify_avatar_request_type(text)
    if request_type in {"capabilities_faq", "meta_external"}:
        mapped = AvatarIntentType.ADVISORY if request_type == "capabilities_faq" else AvatarIntentType.UNKNOWN
        return AvatarLlmIntentFirstResult(
            intent_type=mapped,
            confidence=0.99,
            source="regex",
            explainability={"request_type": request_type, "fast_path": True},
        )

    try:
        from iuve_connect_agents.desktop.ui.avatar_intent_router import is_explicit_device_action_intent

        if is_explicit_device_action_intent(text):
            compound = detect_compound_intent(text)
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.DEVICE_ACTION,
                confidence=0.95,
                is_compound=compound.is_compound,
                source="regex",
                explainability={
                    "fast_path": "explicit_device_action",
                    "categories": list(compound.categories_matched),
                },
            )
    except ImportError:
        pass

    if avatar_llm_intent_first_enabled(trust_state=trust):
        try:
            llm_result = _classify_with_llm_first(text, trust_state=trust)
            if llm_result is not None:
                return llm_result
        except Exception:
            logger.exception("avatar_llm_intent_first_failed")

    return _classify_with_regex_fallback(text)


def resolve_avatar_intent_for_chat(text: str, *, trust_state: str = "") -> AvatarIntentBridgeResult:
    """Resolve request type and compound flag; LLM for device/compound when enabled."""
    request_type = classify_avatar_request_type(text)
    if request_type in {"capabilities_faq", "meta_external"}:
        return AvatarIntentBridgeResult(
            request_type=request_type,
            compound=detect_compound_intent(text),
            source="regex",
        )

    first = classify_avatar_intent_llm(text, trust_state=trust_state)
    if first.source == "llm":
        defer = (
            first.is_compound
            and avatar_execution_plan_enabled()
            and avatar_compound_intent_defer()
        )
        compound = CompoundIntentResult(
            is_compound=first.is_compound or first.intent_type == AvatarIntentType.COMPOUND_PLAN,
            defer_regex=defer,
            categories_matched=tuple((first.explainability or {}).get("categories") or ()),
        )
        mapped_type = "device_task"
        if first.intent_type == AvatarIntentType.COMPOUND_PLAN:
            mapped_type = "device_task"
        return AvatarIntentBridgeResult(
            request_type=mapped_type,
            compound=compound,
            llm_intent=first.intent_type.value,
            source="llm",
        )

    if avatar_llm_intent_enabled(trust_state=trust_state):
        try:
            from app.provider_orchestra.llm_intent_classifier import resolve_avatar_intent

            llm_result = resolve_avatar_intent(text, trust_state=trust_state)
            if llm_result.source == "llm":
                defer = (
                    llm_result.is_compound
                    and avatar_execution_plan_enabled()
                    and avatar_compound_intent_defer()
                )
                compound = CompoundIntentResult(
                    is_compound=llm_result.is_compound,
                    defer_regex=defer,
                    categories_matched=tuple(
                        (llm_result.explainability or {}).get("categories") or ()
                    ),
                )
                mapped_type = llm_result.request_type
                if llm_result.intent.value in {"compound_goal", "research", "open_app"}:
                    mapped_type = "device_task"
                return AvatarIntentBridgeResult(
                    request_type=mapped_type,
                    compound=compound,
                    llm_intent=llm_result.intent.value,
                    source="llm",
                )
        except Exception:
            pass

    return AvatarIntentBridgeResult(
        request_type=request_type,
        compound=detect_compound_intent(text),
        source="regex",
    )


def _classify_with_llm_first(text: str, *, trust_state: str = "") -> AvatarLlmIntentFirstResult | None:
    cloud_result = _classify_via_cloud_api(text, trust_state=trust_state)
    if cloud_result is not None:
        return cloud_result

    system = (
        "You classify user messages for IuVe Desktop Avatar. Return ONLY valid JSON with keys: "
        'intent_type (one of: advisory, device_action, compound_plan, clarify, unknown), '
        "confidence (0.0-1.0), action (command type for device_action: get_diagnostics, "
        "application_launch, browser_open_url, open_folder, open_document_edit, check_update, "
        "keyboard_type_text, mouse_move_relative, empty_trash, or empty), "
        "params (object, e.g. {\"application\": \"firefox\"}), "
        "advisory_answer (optional concise answer for advisory), "
        "clarify_question (optional when clarify), "
        "is_compound (boolean). No markdown. "
        "Understand Russian and English synonyms. "
        "RAM/memory questions → device_action get_diagnostics. "
        "Launch app synonyms (лиса=firefox) → device_action application_launch. "
        "chatgpt / chat.openai / «открой сайт» + «напиши/задай вопрос» → device_action browser_open_url "
        "(is_compound=true only when also fetch/summarize/document steps). "
        "Site + изучи/исследуй + напиши в документ → compound_plan (not open_document_edit alone). "
        "Math/trivia → advisory with advisory_answer."
    )
    user_msg = f"Classify this request:\n{text}"

    raw = _call_llm(system, [{"role": "user", "content": user_msg}])
    if not raw:
        return None

    payload = _parse_intent_json(raw)
    if payload is None:
        return None

    return _intent_result_from_payload(payload, source="llm", source_text=text)


def _classify_with_regex_fallback(text: str) -> AvatarLlmIntentFirstResult:
    _record_regex_routing_path()
    from iuve_connect_agents.desktop.ui.avatar_intent_router import (
        is_generic_app_launch_intent,
        wants_local_cursor_launch,
    )

    compound = detect_compound_intent(text)
    try:
        from iuve_connect_agents.desktop.ui.avatar_intent_router import (
            is_browser_search_navigation_intent,
            is_job_search_intent,
            is_plan_only_intent,
        )
    except ImportError:
        is_browser_search_navigation_intent = lambda _t: False  # type: ignore[assignment,misc]
        is_job_search_intent = lambda _t: False  # type: ignore[assignment,misc]
        is_plan_only_intent = lambda _t: False  # type: ignore[assignment,misc]

    if is_plan_only_intent(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.PLAN_ONLY,
            confidence=0.92,
            is_compound=False,
            source="regex",
            explainability={"fast_path": "plan_only"},
        )

    try:
        from iuve_connect_agents.shared.intent_priority import RootIntent, resolve_root_intent

        resolved = resolve_root_intent(text)
        if resolved.root_intent in {
            RootIntent.DOCUMENT_GENERATION,
            RootIntent.ARTIFACT_GENERATION,
            RootIntent.CODE_GENERATION,
            RootIntent.RESEARCH_THEN_ARTIFACT,
        }:
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.COMPOUND_PLAN,
                confidence=0.91,
                is_compound=True,
                source="regex",
                explainability={
                    "fast_path": resolved.root_intent.value,
                    "priority_tier": resolved.priority_tier,
                },
            )
        if resolved.root_intent == RootIntent.DEVICE_STATUS_QUERY:
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.DEVICE_ACTION,
                confidence=0.9,
                action="get_status",
                source="regex",
                explainability={"fast_path": "device_status_query"},
            )
        if resolved.root_intent == RootIntent.MEMORY_QUERY:
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.ADVISORY,
                confidence=0.88,
                source="regex",
                explainability={"fast_path": "memory_query", "subtype": resolved.subtype},
            )
        if resolved.root_intent == RootIntent.WEB_RESEARCH:
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.COMPOUND_PLAN,
                confidence=0.9,
                is_compound=True,
                source="regex",
                explainability={"fast_path": "web_research"},
            )
        if resolved.root_intent == RootIntent.EXACT_TEXT_INPUT:
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.COMPOUND_PLAN,
                confidence=0.9,
                is_compound=True,
                source="regex",
                explainability={"fast_path": "exact_text_input"},
            )
    except ImportError:
        pass

    if compound.is_compound:
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.COMPOUND_PLAN,
            confidence=0.72,
            is_compound=True,
            source="regex",
            explainability={
                "categories": list(compound.categories_matched),
                "defer_regex": compound.defer_regex,
            },
        )

    try:
        from iuve_connect_agents.desktop.ui.avatar_chat import (
            classify_capability_requirement,
            is_diagnostics_query,
            is_general_chat_query,
        )
        from iuve_connect_agents.desktop.ui.avatar_intent_router import (
            is_active_window_analysis_intent,
            is_folder_analysis_intent,
            is_full_system_metrics_query,
            is_plan_only_intent,
            is_web_research_intent,
        )
    except ImportError:
        is_diagnostics_query = lambda _t: False  # type: ignore[assignment,misc]
        is_general_chat_query = lambda _t: False  # type: ignore[assignment,misc]
        classify_capability_requirement = lambda _t: None  # type: ignore[assignment,misc]
        is_active_window_analysis_intent = lambda _t: False  # type: ignore[assignment,misc]
        is_folder_analysis_intent = lambda _t: False  # type: ignore[assignment,misc]
        is_web_research_intent = lambda _t: False  # type: ignore[assignment,misc]
        is_full_system_metrics_query = lambda _t: False  # type: ignore[assignment,misc]
        is_plan_only_intent = lambda _t: False  # type: ignore[assignment,misc]

    if is_active_window_analysis_intent(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.COMPOUND_PLAN,
            confidence=0.9,
            is_compound=True,
            source="regex",
            explainability={"fast_path": "active_window_analysis"},
        )

    if is_folder_analysis_intent(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.COMPOUND_PLAN,
            confidence=0.9,
            is_compound=True,
            source="regex",
            explainability={"fast_path": "folder_analysis"},
        )

    if is_web_research_intent(text):
        fast_path = "job_search" if is_job_search_intent(text) else "web_research"
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.COMPOUND_PLAN,
            confidence=0.88,
            is_compound=True,
            source="regex",
            explainability={"fast_path": fast_path},
        )

    if is_browser_search_navigation_intent(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.COMPOUND_PLAN,
            confidence=0.9,
            is_compound=True,
            source="regex",
            explainability={"fast_path": "browser_search_navigation"},
        )

    try:
        from iuve_connect_agents.desktop.ui.avatar_intent_router import is_local_computer_job_search_intent

        if is_local_computer_job_search_intent(text):
            return AvatarLlmIntentFirstResult(
                intent_type=AvatarIntentType.COMPOUND_PLAN,
                confidence=0.86,
                is_compound=True,
                source="regex",
                explainability={"fast_path": "local_computer_job_search"},
            )
    except ImportError:
        pass

    if is_full_system_metrics_query(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.DEVICE_ACTION,
            confidence=0.85,
            action="get_diagnostics",
            params={"format_table": True},
            source="regex",
        )

    if is_plan_only_intent(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.PLAN_ONLY,
            confidence=0.88,
            is_compound=False,
            source="regex",
            explainability={"fast_path": "plan_only"},
        )

    if is_diagnostics_query(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.DEVICE_ACTION,
            confidence=0.68,
            action="get_diagnostics",
            source="regex",
        )

    if is_generic_app_launch_intent(text) or wants_local_cursor_launch(text):
        from iuve_connect_agents.desktop.active.device_capability_scanner import normalize_app_query_aliases
        from iuve_connect_agents.desktop.ui.avatar_intent_router import extract_app_query_from_text

        query = normalize_app_query_aliases(extract_app_query_from_text(text) or "application")
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.DEVICE_ACTION,
            confidence=0.66,
            action="application_launch",
            params={"application": query},
            source="regex",
        )

    if classify_capability_requirement(text):
        cap = classify_capability_requirement(text)
        action = "mouse_move_relative" if cap == "mouse_control" else "keyboard_type_text"
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.DEVICE_ACTION,
            confidence=0.64,
            action=action,
            source="regex",
        )

    if is_general_chat_query(text):
        return AvatarLlmIntentFirstResult(
            intent_type=AvatarIntentType.ADVISORY,
            confidence=0.62,
            source="regex",
        )

    return AvatarLlmIntentFirstResult(
        intent_type=AvatarIntentType.UNKNOWN,
        confidence=0.4,
        source="regex",
    )


def _record_regex_routing_path() -> None:
    try:
        from iuve_connect_agents.desktop.orchestra_routing_tracker import (
            OrchestraRoutingPath,
            record_routing_path,
        )

        record_routing_path(OrchestraRoutingPath.REGEX)
    except ImportError:
        pass


def _classify_via_cloud_api(text: str, *, trust_state: str = "") -> AvatarLlmIntentFirstResult | None:
    try:
        from iuve_connect_agents.shared.avatar_llm_cloud import request_cloud_intent_classify
    except ImportError:
        return None

    payload = request_cloud_intent_classify(text, trust_state=trust_state)
    if not payload or not payload.get("ok"):
        return None

    result = payload.get("result")
    if not isinstance(result, dict):
        return None
    return _intent_result_from_payload(result, source="llm", explainability_extra={"backend": "cloud_orchestra"}, source_text=text)


def _classify_with_llm_first_server(
    text: str,
    *,
    user_id: str,
    db: Any,
    trust_state: str = "",
) -> AvatarLlmIntentFirstResult | None:
    """In-process cloud orchestra intent classify (backend API handler only)."""
    system = (
        "You classify user messages for IuVe Desktop Avatar. Return ONLY valid JSON with keys: "
        'intent_type (one of: advisory, device_action, compound_plan, clarify, unknown), '
        "confidence (0.0-1.0), action (command type for device_action: get_diagnostics, "
        "application_launch, browser_open_url, open_folder, open_document_edit, check_update, "
        "keyboard_type_text, mouse_move_relative, empty_trash, or empty), "
        "params (object, e.g. {\"application\": \"firefox\"}), "
        "advisory_answer (optional concise answer for advisory), "
        "clarify_question (optional when clarify), "
        "is_compound (boolean). No markdown. "
        "Understand Russian and English synonyms. "
        "RAM/memory questions → device_action get_diagnostics. "
        "Launch app synonyms (лиса=firefox) → device_action application_launch. "
        "chatgpt / chat.openai / «открой сайт» + «напиши/задай вопрос» → device_action browser_open_url "
        "(is_compound=true only when also fetch/summarize/document steps). "
        "Site + изучи/исследуй + напиши в документ → compound_plan (not open_document_edit alone). "
        "Math/trivia → advisory with advisory_answer."
    )
    user_msg = f"Classify this request:\n{text}"
    raw = _call_in_process_orchestra_llm(
        system,
        [{"role": "user", "content": user_msg}],
        user_id=user_id,
        db=db,
        user_text=text,
    )
    if not raw:
        return None

    payload = _parse_intent_json(raw)
    if payload is None:
        return None
    return _intent_result_from_payload(payload, source="llm", source_text=text)


def _correct_intent_for_local_context(
    text: str,
    intent_type: AvatarIntentType,
    action: str,
    *,
    is_compound: bool,
) -> tuple[AvatarIntentType, str, bool]:
    try:
        from iuve_connect_agents.desktop.ui.avatar_intent_router import (
            is_chatgpt_browser_intent,
            is_research_write_compound_intent,
            wants_browser_open_and_type,
        )
    except ImportError:
        return intent_type, action, is_compound

    if wants_browser_open_and_type(text) or is_chatgpt_browser_intent(text):
        if action in {"open_document_edit", "application_launch", ""}:
            return AvatarIntentType.DEVICE_ACTION, "browser_open_url", True
        if intent_type == AvatarIntentType.COMPOUND_PLAN and action == "open_document_edit":
            return AvatarIntentType.DEVICE_ACTION, "browser_open_url", True

    if is_research_write_compound_intent(text):
        if action == "open_document_edit" or (
            intent_type == AvatarIntentType.DEVICE_ACTION and action in {"open_document_edit", ""}
        ):
            return AvatarIntentType.COMPOUND_PLAN, "", True

    return intent_type, action, is_compound


def _intent_result_from_payload(
    payload: dict[str, Any],
    *,
    source: str = "llm",
    explainability_extra: dict[str, Any] | None = None,
    source_text: str = "",
) -> AvatarLlmIntentFirstResult | None:
    intent_raw = str(payload.get("intent_type") or "").strip().lower()
    if intent_raw not in _VALID_INTENT_TYPES:
        return None

    intent_type = AvatarIntentType(intent_raw)
    confidence = float(payload.get("confidence") or 0.75)
    action = str(payload.get("action") or "").strip().lower()
    params = payload.get("params")
    if not isinstance(params, dict):
        params = {}
    advisory_answer = str(payload.get("advisory_answer") or "").strip() or None
    clarify_question = str(payload.get("clarify_question") or "").strip() or None
    is_compound = bool(payload.get("is_compound")) or intent_type == AvatarIntentType.COMPOUND_PLAN

    intent_type, action, is_compound = _correct_intent_for_local_context(
        str(source_text or ""),
        intent_type,
        action,
        is_compound=is_compound,
    )

    if confidence < _CONFIDENCE_CLARIFY_THRESHOLD and intent_type not in {
        AvatarIntentType.CLARIFY,
        AvatarIntentType.ADVISORY,
    }:
        intent_type = AvatarIntentType.CLARIFY
        clarify_question = clarify_question or "Уточните, пожалуйста, что именно нужно сделать?"

    explainability = {"raw_intent_type": intent_raw}
    if explainability_extra:
        explainability.update(explainability_extra)

    return AvatarLlmIntentFirstResult(
        intent_type=intent_type,
        confidence=min(0.99, max(0.0, confidence)),
        action=action,
        params=dict(params),
        advisory_answer=advisory_answer,
        clarify_question=clarify_question,
        is_compound=is_compound,
        source=source,
        explainability=explainability,
    )


def _call_llm(system: str, context: list[dict[str, Any]]) -> str:
    """Cloud orchestra first; local Ollama only as optional fallback."""
    in_process = _call_in_process_orchestra_llm(system, context)
    if in_process:
        return in_process

    if avatar_local_ollama_fallback_enabled() and avatar_local_ollama_reachable():
        raw = _call_local_ollama(system, context)
        if raw:
            try:
                from iuve_connect_agents.desktop.orchestra_routing_tracker import (
                    OrchestraRoutingPath,
                    record_routing_path,
                )

                record_routing_path(OrchestraRoutingPath.LOCAL_OLLAMA)
            except ImportError:
                pass
        return raw
    try:
        from iuve_connect_agents.desktop.orchestra_routing_tracker import (
            OrchestraRoutingPath,
            record_routing_path,
        )

        record_routing_path(OrchestraRoutingPath.UNAVAILABLE)
    except ImportError:
        pass
    return ""


def _call_in_process_orchestra_llm(
    system: str,
    context: list[dict[str, Any]],
    *,
    user_id: str = "",
    db: Any = None,
    user_text: str = "",
) -> str:
    from iuve_connect_agents.shared.feature_flags import avatar_provider_orchestra_enabled

    if not avatar_provider_orchestra_enabled():
        return ""
    try:
        from app.provider_orchestra.orchestra_service import orchestra_service
        from app.provider_orchestra.types import OrchestraRequest
    except ImportError:
        return ""

    resolved_text = str(user_text or "").strip()
    if not resolved_text:
        for item in context:
            if isinstance(item, dict) and item.get("role") == "user":
                resolved_text = str(item.get("content") or "")
                break

    try:
        if not orchestra_service.is_enabled():
            return ""
        response = orchestra_service.route_request(
            OrchestraRequest(user_id=str(user_id or ""), text=resolved_text),
            db=db,
        )
        payload = orchestra_service.invoke_for_execution(
            fallback_chain=list(response.decision.fallback_chain or []),
            sub_model=response.decision.selected_sub_model,
            system_prompt=system,
            context=context,
        )
        if payload.get("success"):
            return str(payload.get("content") or "").strip()
    except Exception:
        logger.exception("avatar_llm_intent_orchestra_failed")
    return ""


def _call_local_ollama(system: str, context: list[dict[str, Any]]) -> str:
    from iuve_connect_agents.shared.avatar_llm_advisory import (
        _extract_message_content,
        _post_ollama_chat,
        advisory_ollama_base_url,
        advisory_ollama_model,
        advisory_ollama_timeout_sec,
    )

    messages = [{"role": "system", "content": system}, *context]
    try:
        response = _post_ollama_chat(
            base_url=advisory_ollama_base_url(),
            model=advisory_ollama_model(),
            messages=messages,
            timeout_sec=advisory_ollama_timeout_sec(),
        )
    except Exception:
        return ""
    return _extract_message_content(response)


def _parse_intent_json(raw: str) -> dict[str, Any] | None:
    text = str(raw or "").strip()
    if not text:
        return None
    if text.startswith("```"):
        text = re.sub(r"^```(?:json)?\s*", "", text)
        text = re.sub(r"\s*```$", "", text)
    try:
        payload = json.loads(text)
    except json.JSONDecodeError:
        match = re.search(r"\{[\s\S]*\}", text)
        if not match:
            return None
        try:
            payload = json.loads(match.group(0))
        except json.JSONDecodeError:
            return None
    return payload if isinstance(payload, dict) else None
