import json
import re

from app.modules.agent.engine.router.schemas import RouteDecision, RouterContext
from app.modules.agent.llm import AgentLlmService


class IntentClassifier:
    _short_confirmations = {"да", "ок", "делай", "поехали", "запускай"}
    _route_mapping = {
        "default/general": ("default", "general"),
        "project/qa": ("project", "qa"),
        "project/edits": ("project", "edits"),
        "docs/generation": ("docs", "generation"),
    }

    def __init__(self, llm: AgentLlmService) -> None:
        self._llm = llm

    def classify_new_intent(self, user_message: str, context: RouterContext) -> RouteDecision:
        text = (user_message or "").strip().lower()
        if text in self._short_confirmations and context.last_routing:
            return RouteDecision(
                domain_id=context.last_routing["domain_id"],
                process_id=context.last_routing["process_id"],
                confidence=1.0,
                reason="short_confirmation",
                use_previous=True,
                decision_type="continue",
            )

        deterministic = self._deterministic_route(text)
        if deterministic:
            return deterministic

        llm_decision = self._classify_with_llm(user_message, context)
        if llm_decision:
            return llm_decision

        return RouteDecision(
            domain_id="default",
            process_id="general",
            confidence=0.8,
            reason="default",
            decision_type="start",
        )

    def from_mode(self, mode: str) -> RouteDecision | None:
        mapping = {
            "project_qa": ("project", "qa"),
            "project_edits": ("project", "edits"),
            "docs_generation": ("docs", "generation"),
            # Legacy aliases kept for API compatibility.
            "analytics_review": ("project", "qa"),
            "code_change": ("project", "edits"),
            "qa": ("default", "general"),
        }
        route = mapping.get((mode or "auto").strip().lower())
        if not route:
            return None
        return RouteDecision(
            domain_id=route[0],
            process_id=route[1],
            confidence=1.0,
            reason=f"mode_override:{mode}",
            decision_type="switch",
            explicit_switch=True,
        )

    def _classify_with_llm(self, user_message: str, context: RouterContext) -> RouteDecision | None:
        history = context.message_history[-8:]
        user_input = json.dumps(
            {
                "message": user_message,
                "history": history,
                "allowed_routes": list(self._route_mapping.keys()),
            },
            ensure_ascii=False,
        )
        try:
            raw = self._llm.generate("router_intent", user_input).strip()
        except Exception:
            return None

        payload = self._parse_llm_payload(raw)
        if not payload:
            return None

        route = self._route_mapping.get(payload["route"])
        if not route:
            return None

        confidence = self._normalize_confidence(payload.get("confidence"))
        return RouteDecision(
            domain_id=route[0],
            process_id=route[1],
            confidence=confidence,
            reason=f"llm_router:{payload.get('reason', 'ok')}",
            decision_type="start",
        )

    def _parse_llm_payload(self, raw: str) -> dict[str, str | float] | None:
        candidate = self._strip_code_fence(raw.strip())
        if not candidate:
            return None
        try:
            parsed = json.loads(candidate)
        except json.JSONDecodeError:
            return None
        if not isinstance(parsed, dict):
            return None
        route = str(parsed.get("route", "")).strip().lower()
        if not route:
            return None
        return {
            "route": route,
            "confidence": parsed.get("confidence"),
            "reason": str(parsed.get("reason", "ok")).strip().lower(),
        }

    def _normalize_confidence(self, value: object) -> float:
        if isinstance(value, (float, int)):
            return max(0.0, min(1.0, float(value)))
        return 0.75

    def _strip_code_fence(self, text: str) -> str:
        if not text.startswith("```"):
            return text
        lines = text.splitlines()
        if len(lines) < 3:
            return text
        if lines[-1].strip() != "```":
            return text
        return "\n".join(lines[1:-1]).strip()

    def _deterministic_route(self, text: str) -> RouteDecision | None:
        if self._is_targeted_file_edit_request(text):
            return RouteDecision(
                domain_id="project",
                process_id="edits",
                confidence=0.97,
                reason="deterministic_targeted_file_edit",
                decision_type="switch",
                explicit_switch=True,
            )
        if self._is_broad_docs_request(text):
            return RouteDecision(
                domain_id="docs",
                process_id="generation",
                confidence=0.95,
                reason="deterministic_docs_generation",
                decision_type="switch",
                explicit_switch=True,
            )
        return None

    def _is_targeted_file_edit_request(self, text: str) -> bool:
        if not text:
            return False
        edit_markers = (
            "добавь",
            "добавить",
            "измени",
            "исправь",
            "обнови",
            "удали",
            "замени",
            "вставь",
            "в конец",
            "в начале",
            "append",
            "update",
            "edit",
            "remove",
            "replace",
        )
        has_edit_marker = any(marker in text for marker in edit_markers)
        has_file_marker = (
            "readme" in text
            or bool(re.search(r"\b[\w.\-/]+\.(md|txt|rst|yaml|yml|json|toml|ini|cfg)\b", text))
        )
        return has_edit_marker and has_file_marker

    def _is_broad_docs_request(self, text: str) -> bool:
        if not text:
            return False
        docs_markers = (
            "подготовь документац",
            "сгенерируй документац",
            "создай документац",
            "опиши документац",
            "generate documentation",
            "write documentation",
            "docs/",
        )
        return any(marker in text for marker in docs_markers)
