from app.core.config import settings
from app.plugins.base import PluginRegistry
from app.plugins.mock import MockPluginExecutor
from app.services.agent_service import AgentService
from app.services.classifier import (
    BertIntentClassifier,
    IntentClassifier,
    JointBertIntentClassifier,
    MockIntentClassifier,
    RemoteIntentClassifier,
)
from app.services.config_loader import ConfigLoader
from app.services.intent_registry import IntentRegistry
from app.services.joint_nlu import JointBertNLU
from app.services.knowledge_llm import DashScopeKnowledgeLLM
from app.services.knowledge_store import KnowledgeStore
from app.services.multi_intent_detector import (
    BertMultiIntentDetector,
    JointBertMultiIntentDetector,
    MultiIntentDetector,
)
from app.services.planner import (
    CompositeWorkflowPlanner,
    DashScopeWorkflowPlanner,
    HeuristicWorkflowPlanner,
    TemplateWorkflowPlanner,
    WorkflowPlanner,
)
from app.services.response_policy import ResponsePolicy
from app.services.rewrite_engine import ContextRewriteEngine
from app.services.router import (
    HeuristicSlotExtractor,
    JointBertSlotExtractor,
    IntentRouter,
    Router,
    build_matcher_pipeline,
)
from app.services.session_store import InMemorySessionStore, RedisSessionStore, SessionStore
from app.services.social import DashScopeSocialResponder, SocialResponder, SocialRouter


def build_session_store(session_backend: str | None = None) -> SessionStore:
    backend = session_backend or settings.session_backend
    if backend == "memory":
        return InMemorySessionStore()
    if backend == "redis":
        return RedisSessionStore(
            redis_url=settings.redis_url,
            key_prefix=settings.redis_key_prefix,
            ttl_seconds=settings.session_ttl_seconds,
        )
    raise ValueError(f"Unsupported session backend: {backend}")


def build_router(
    intent_registry: IntentRegistry,
    matcher_pipeline: str | None = None,
    classifier_backend: str | None = None,
    classifier: IntentClassifier | None = None,
    joint_nlu: JointBertNLU | None = None,
) -> Router:
    active_pipeline = matcher_pipeline or settings.matcher_pipeline
    matcher_stages = [stage.strip() for stage in active_pipeline.split(",") if stage.strip()]
    if not matcher_stages:
        matcher_stages = ["classifier"]
    if matcher_stages != ["classifier"]:
        raise ValueError("Only classifier matcher pipeline is supported in bert-first mode")
    if settings.slot_extractor_backend not in {"heuristic", "joint_bert"}:
        raise ValueError(f"Unsupported slot extractor backend: {settings.slot_extractor_backend}")
    classifier = classifier or build_classifier(
        matcher_pipeline=active_pipeline,
        classifier_backend=classifier_backend,
        joint_nlu=joint_nlu,
    )
    if settings.slot_extractor_backend == "heuristic":
        slot_extractor = HeuristicSlotExtractor()
    else:
        if joint_nlu is None:
            raise ValueError("slot_extractor_backend=joint_bert requires a Joint NLU runtime")
        slot_extractor = JointBertSlotExtractor(joint_nlu)
    return IntentRouter(
        matcher=build_matcher_pipeline(
            intent_registry,
            matcher_stages,
            classifier=classifier,
            route_to_cloud_threshold=settings.local_route_to_cloud_threshold,
            clarify_margin_threshold=settings.local_clarify_margin_threshold,
            classifier_execute_score_threshold=settings.local_classifier_execute_score_threshold,
            classifier_execute_margin_threshold=settings.local_classifier_execute_margin_threshold,
        ),
        slot_extractor=slot_extractor,
    )


def build_classifier(
    matcher_pipeline: str | None = None,
    classifier_backend: str | None = None,
    joint_nlu: JointBertNLU | None = None,
) -> IntentClassifier | None:
    active_pipeline = matcher_pipeline or settings.matcher_pipeline
    active_backend = classifier_backend or settings.classifier_backend
    if "classifier" not in active_pipeline:
        return None
    fallback = MockIntentClassifier(
        threshold=settings.classifier_threshold,
        top_k=settings.classifier_top_k,
    )
    if active_backend == "mock":
        return fallback
    if active_backend == "bert":
        classifier = BertIntentClassifier(
            model_path=settings.classifier_model_path,
            threshold=settings.classifier_bert_threshold,
            label_map_path=settings.classifier_label_map_path or None,
            fallback=fallback,
            top_k=settings.classifier_top_k,
        )
        if settings.classifier_warmup_enabled:
            classifier.warmup(settings.classifier_warmup_text)
        return classifier
    if active_backend == "joint_bert":
        runtime = joint_nlu or build_joint_nlu()
        classifier = JointBertIntentClassifier(
            nlu=runtime,
            threshold=settings.joint_nlu_intent_threshold if settings.joint_nlu_intent_threshold > 0 else 0.0,
            top_k=settings.joint_nlu_top_k,
        )
        if settings.classifier_warmup_enabled:
            classifier.warmup(settings.classifier_warmup_text)
        return classifier
    if active_backend == "remote":
        return RemoteIntentClassifier(
            endpoint=settings.classifier_remote_url,
            timeout_seconds=settings.classifier_remote_timeout_seconds,
            threshold=settings.classifier_threshold,
            fallback=fallback,
            label_map_path=settings.classifier_label_map_path or None,
            top_k=settings.classifier_top_k,
        )
    raise ValueError(f"Unsupported classifier backend: {active_backend}")


def build_agent_service() -> AgentService:
    return build_agent_service_with_runtime()


def build_agent_service_with_runtime(
    matcher_pipeline: str | None = None,
    classifier_backend: str | None = None,
    session_backend: str | None = None,
) -> AgentService:
    runtime_bundle = load_runtime_bundle()
    intent_registry = runtime_bundle.intent_registry
    active_classifier_backend = classifier_backend or settings.classifier_backend
    needs_joint_nlu = active_classifier_backend == "joint_bert" or settings.slot_extractor_backend == "joint_bert"
    joint_nlu = build_joint_nlu() if needs_joint_nlu else None
    classifier = build_classifier(
        matcher_pipeline=matcher_pipeline or settings.matcher_pipeline,
        classifier_backend=active_classifier_backend,
        joint_nlu=joint_nlu,
    )
    planner_clause_classifier = (
        classifier
        if settings.planner_clause_classifier_enabled and active_classifier_backend in {"bert", "remote", "joint_bert"}
        else None
    )
    multi_intent_detector = build_multi_intent_detector(
        classifier_backend=classifier_backend,
        joint_nlu=joint_nlu,
    )
    plugin_registry = PluginRegistry()
    MockPluginExecutor().register(plugin_registry)
    return AgentService(
        intent_registry=intent_registry,
        router=build_router(
            intent_registry,
            matcher_pipeline=matcher_pipeline,
            classifier_backend=active_classifier_backend,
            classifier=classifier,
            joint_nlu=joint_nlu,
        ),
        plugins=plugin_registry,
        session_store=build_session_store(session_backend=session_backend),
        rewrite_engine=runtime_bundle.rewrite_engine,
        response_policy=ResponsePolicy(
            templates=runtime_bundle.response_templates,
            intent_hints=runtime_bundle.intent_hints,
        ),
        dialog_rules=runtime_bundle.dialog_rules,
        dialog_act_engine=runtime_bundle.dialog_act_engine,
        planner=build_planner(
            runtime_bundle.workflow_templates,
            clause_classifier=planner_clause_classifier,
            multi_intent_detector=multi_intent_detector,
            joint_nlu=joint_nlu,
        ),
        social_router=SocialRouter(),
        social_responder=build_social_responder(),
        knowledge_llm=build_knowledge_llm(),
    )


def build_intent_registry() -> IntentRegistry:
    return load_runtime_bundle().intent_registry


def load_runtime_bundle():
    return ConfigLoader(
        domain_path=settings.domain_config_path,
        action_path=settings.action_config_path,
        response_path=settings.response_config_path,
        form_path=settings.form_config_path,
        rule_path=settings.rule_config_path,
        dialog_act_path=settings.dialog_act_config_path,
        workflow_path=settings.workflow_config_path,
        legacy_intent_path=settings.intent_config_path,
        context_rewrite_path=settings.context_rewrite_config_path,
    ).load()


def build_joint_nlu() -> JointBertNLU:
    runtime = JointBertNLU(
        model_path=settings.joint_nlu_model_path,
        intent_threshold=settings.joint_nlu_intent_threshold if settings.joint_nlu_intent_threshold > 0 else None,
        top_k=settings.joint_nlu_top_k,
    )
    if settings.classifier_warmup_enabled:
        runtime.warmup(settings.classifier_warmup_text)
    return runtime


def build_multi_intent_detector(
    classifier_backend: str | None = None,
    joint_nlu: JointBertNLU | None = None,
) -> MultiIntentDetector | None:
    active_backend = classifier_backend or settings.classifier_backend
    if not settings.planner_multi_intent_detector_enabled:
        return None
    if active_backend not in {"bert", "joint_bert"}:
        return None
    if active_backend == "joint_bert":
        runtime = joint_nlu or build_joint_nlu()
        detector = JointBertMultiIntentDetector(
            nlu=runtime,
            threshold=settings.planner_multi_intent_detector_threshold if settings.planner_multi_intent_detector_threshold > 0 else None,
            top_k=settings.planner_multi_intent_detector_top_k,
            max_labels=settings.planner_multi_intent_detector_max_labels,
        )
        if settings.classifier_warmup_enabled:
            detector.warmup(settings.classifier_warmup_text)
        return detector
    detector_model_path = settings.planner_multi_intent_detector_model_path or settings.classifier_model_path
    detector = BertMultiIntentDetector(
        model_path=detector_model_path,
        threshold=settings.planner_multi_intent_detector_threshold,
        top_k=settings.planner_multi_intent_detector_top_k,
        max_labels=settings.planner_multi_intent_detector_max_labels,
    )
    if settings.classifier_warmup_enabled:
        detector.warmup(settings.classifier_warmup_text)
    return detector


def build_planner(
    workflow_templates=None,
    clause_classifier: IntentClassifier | None = None,
    multi_intent_detector: MultiIntentDetector | None = None,
    joint_nlu: JointBertNLU | None = None,
) -> WorkflowPlanner:
    template_planner = TemplateWorkflowPlanner(
        workflow_templates,
        clause_classifier=clause_classifier,
        multi_intent_detector=multi_intent_detector,
        joint_nlu=joint_nlu,
        classifier_weight=settings.planner_clause_classifier_weight,
        model_only_threshold=settings.planner_clause_model_only_threshold,
    )
    local_first = CompositeWorkflowPlanner(
        [
            template_planner,
            HeuristicWorkflowPlanner(
                clause_classifier=clause_classifier,
                multi_intent_detector=multi_intent_detector,
                joint_nlu=joint_nlu,
                classifier_weight=settings.planner_clause_classifier_weight,
                model_only_threshold=settings.planner_clause_model_only_threshold,
            ),
        ]
    )
    if settings.planner_backend == "heuristic":
        return local_first
    if settings.planner_backend == "dashscope":
        cloud_planner = DashScopeWorkflowPlanner(
            base_url=settings.planner_base_url,
            api_key=settings.planner_api_key,
            model_name=settings.planner_model_name,
            timeout_seconds=settings.planner_timeout_seconds,
            fallback=local_first,
            joint_nlu=joint_nlu,
        )
        return CompositeWorkflowPlanner([local_first, cloud_planner])
    raise ValueError(f"Unsupported planner backend: {settings.planner_backend}")


def build_social_responder() -> SocialResponder:
    return DashScopeSocialResponder(
        base_url=settings.planner_base_url,
        api_key=settings.planner_api_key,
        model_name=settings.planner_model_name,
        timeout_seconds=settings.planner_timeout_seconds,
    )


def build_knowledge_llm() -> DashScopeKnowledgeLLM:
    """构建知识库 LLM 问答器（与 planner 共用 DashScope 配置）。"""
    store = KnowledgeStore(settings.knowledge_dir)
    return DashScopeKnowledgeLLM(
        base_url=settings.planner_base_url,
        api_key=settings.planner_api_key,
        model_name=settings.planner_model_name,
        knowledge_store=store,
        timeout_seconds=12.0,
    )
