"""
Semantic Matcher for Ensemble Matching System.

TASK-053-4: Wrapper around WorkflowIntentClassifier for IMatcher interface.

This is a refactor, NOT a replacement. Existing SemanticWorkflowMatcher
is kept for backward compatibility. This new SemanticMatcher is specifically
designed for ensemble matching.
"""

import logging
from typing import TYPE_CHECKING, Any, Dict, Optional

from server.router.domain.entities.ensemble import MatcherResult
from server.router.domain.interfaces.matcher import IMatcher
from server.router.infrastructure.config import RouterConfig

if TYPE_CHECKING:
    from server.router.application.classifier.workflow_intent_classifier import (
        WorkflowIntentClassifier,
    )
    from server.router.application.workflows.registry import WorkflowRegistry

logger = logging.getLogger(__name__)


class SemanticMatcher(IMatcher):
    """Matches workflows using LaBSE embeddings.

    Wrapper around WorkflowIntentClassifier for IMatcher interface.
    Does NOT include keyword matching (that's KeywordMatcher's job).

    This matcher provides flexible, context-aware matching using
    semantic similarity between prompts and workflow descriptions.

    Example:
        >>> matcher = SemanticMatcher(classifier, registry, weight=0.40)
        >>> result = matcher.match("make a mobile device")
        >>> print(result.workflow_name)  # "phone_workflow"
        >>> print(result.confidence)      # 0.84
    """

    def __init__(
        self,
        classifier: "WorkflowIntentClassifier",
        registry: "WorkflowRegistry",
        config: Optional[RouterConfig] = None,
        weight: float = 0.40,
    ):
        """Initialize semantic matcher.

        Args:
            classifier: WorkflowIntentClassifier for semantic matching.
            registry: Workflow registry for workflow info.
            config: Router configuration.
            weight: Weight for ensemble aggregation (default 0.40).
        """
        self._classifier = classifier
        self._registry = registry
        self._config = config or RouterConfig()
        self._weight = weight
        self._is_initialized = False

    @property
    def name(self) -> str:
        """Matcher name for logging and aggregation.

        Returns:
            "semantic"
        """
        return "semantic"

    @property
    def weight(self) -> float:
        """Weight for score aggregation.

        Standard weight: 0.40 (flexible, context-aware).

        Returns:
            Weight value between 0.0 and 1.0.
        """
        return self._weight

    def initialize(self, registry: "WorkflowRegistry") -> None:
        """Initialize embeddings for all workflows.

        Must be called before matching.

        Args:
            registry: Workflow registry containing workflows to index.
        """
        workflows: Dict[str, Any] = {}
        for name in registry.get_all_workflows():
            workflow = registry.get_workflow(name)
            if workflow:
                workflows[name] = workflow
            else:
                definition = registry.get_definition(name)
                if definition:
                    workflows[name] = definition

        self._classifier.load_workflow_embeddings(workflows)
        self._is_initialized = True
        logger.info(f"SemanticMatcher initialized with {len(workflows)} workflows")

    def is_initialized(self) -> bool:
        """Check if matcher is initialized.

        Returns:
            True if initialize() was called, False otherwise.
        """
        return self._is_initialized

    def match(self, prompt: str, context: Optional[Dict[str, Any]] = None) -> MatcherResult:
        """Match prompt using semantic similarity.

        Uses LaBSE embeddings via WorkflowIntentClassifier.
        Does NOT check keywords (that's KeywordMatcher's responsibility).

        TASK-055-FIX (Bug 4): Returns RAW scores without filtering by confidence_level.
        Let EnsembleAggregator decide with normalized thresholds.
        This fixes prompts with numbers like "stol z nogami pod katem 45 stopni"
        which would otherwise be rejected due to lower semantic similarity.

        Args:
            prompt: User prompt/goal (e.g., "make a mobile device").
            context: Optional scene context (not used by semantic matcher).

        Returns:
            MatcherResult with workflow name and confidence if matched,
            or MatcherResult with workflow_name=None and confidence=0.0 if no match.

        Example:
            >>> result = matcher.match("create a mobile phone")
            >>> result.workflow_name  # "phone_workflow"
            >>> result.confidence     # 0.84
            >>> result.weighted_score # 0.336 (0.84 × 0.40)
        """
        if not self._is_initialized:
            logger.warning("SemanticMatcher not initialized, returning no match")
            return MatcherResult(
                matcher_name=self.name,
                workflow_name=None,
                confidence=0.0,
                weight=self.weight,
                metadata={"error": "Not initialized"},
            )

        # Use classifier's find_best_match_with_confidence
        result = self._classifier.find_best_match_with_confidence(prompt)

        workflow_id = result.get("workflow_id")
        score = result.get("score", 0.0)
        confidence_level = result.get("confidence_level", "NONE")

        # TASK-055-FIX (Bug 4): Return result regardless of confidence_level
        # Let EnsembleAggregator decide with normalized thresholds
        # If classifier set workflow_id to None due to threshold,
        # get it from fallback_candidates
        if workflow_id is None and score > 0 and result.get("fallback_candidates"):
            best_fallback = result["fallback_candidates"][0]
            workflow_id = best_fallback.get("workflow_id")
            logger.debug(
                f"SemanticMatcher: Using fallback candidate {workflow_id} (score={score:.3f}, level={confidence_level})"
            )

        # Return match if we have a workflow (regardless of confidence_level)
        if workflow_id and score > 0:
            return MatcherResult(
                matcher_name=self.name,
                workflow_name=workflow_id,
                confidence=score,  # Raw score, let aggregator normalize
                weight=self.weight,
                metadata={
                    "confidence_level": confidence_level,
                    "source_type": result.get("source_type"),
                    "matched_text": result.get("matched_text"),
                    "language_detected": result.get("language_detected"),
                },
            )

        # No match at all (score is 0 or no candidates)
        return MatcherResult(
            matcher_name=self.name,
            workflow_name=None,
            confidence=0.0,
            weight=self.weight,
            metadata={"confidence_level": confidence_level},
        )
