"""Classification of entity mentions as explicit, inferred, or unresolved.

This module provides the logic to determine whether a company mention in a
document is:
- Explicit: the company name, ticker, or known alias appears directly in text
- Inferred: the company relationship is derived from context (competitor,
  supplier, sector peer) rather than a direct textual reference
- Unresolved: no match in the registry — preserved as literal text

The classifier operates on already-resolved candidates from the SymbolResolver,
using document context and relationship signals to make the determination.
"""

from __future__ import annotations

import re
from enum import Enum

from services.intelligence_pipeline_v3.resolution.models import (
    MentionType,
    ResolutionCandidate,
)

# Relationship keywords that suggest inferred exposure rather than direct mention.
_INFERRED_KEYWORDS = re.compile(
    r"\b("
    r"competitor|competitors|rival|rivals|"
    r"supplier|suppliers|vendor|vendors|"
    r"customer|customers|client|clients|"
    r"partner|partners|peer|peers|"
    r"sector\s+peer|industry\s+peer|"
    r"supply\s+chain|downstream|upstream|"
    r"exposed\s+to|exposure|"
    r"indirectly|second[- ]order|knock[- ]on"
    r")\b",
    re.IGNORECASE,
)

# Direct mention keywords that confirm explicit reference.
_EXPLICIT_KEYWORDS = re.compile(
    r"\b("
    r"announced|reported|said|stated|disclosed|"
    r"according\s+to|shares\s+of|stock\s+of|"
    r"CEO\s+of|CFO\s+of|spokesperson\s+for"
    r")\b",
    re.IGNORECASE,
)


class ClassifiedMentionType(str, Enum):
    """Extended mention classification with unresolved state.

    This enum adds `unresolved` to the base MentionType for full classification
    including cases where no registry match exists.
    """

    explicit_mention = "explicit_mention"
    inferred_exposure = "inferred_exposure"
    unresolved = "unresolved"


def classify_mention(
    mention: str,
    document_context: str,
    resolved_candidates: list[ResolutionCandidate],
) -> ClassifiedMentionType:
    """Classify a mention as explicit, inferred, or unresolved.

    Decision logic:
    1. If no candidates resolved → unresolved
    2. If the mention text (ticker/name/alias) appears directly in the context
       without surrounding inferred-relationship keywords → explicit
    3. If surrounding context contains relationship/exposure keywords
       (competitor, supplier, peer, etc.) → inferred
    4. Default to explicit if the mention resolves to a candidate (direct
       textual match in the alias index implies explicit reference)

    Args:
        mention: The original text that was resolved (or attempted).
        document_context: Surrounding text from the document for context analysis.
        resolved_candidates: Candidates returned by the SymbolResolver.

    Returns:
        ClassifiedMentionType indicating the nature of the mention.
    """
    # No candidates → unresolved.
    if not resolved_candidates:
        return ClassifiedMentionType.unresolved

    # Check if inferred-relationship keywords are near the mention in context.
    if document_context and _has_inferred_context(mention, document_context):
        return ClassifiedMentionType.inferred_exposure

    # The mention resolved via the alias index (ticker, name, or alias match),
    # which means the text itself references the company directly.
    return ClassifiedMentionType.explicit_mention


def _has_inferred_context(mention: str, context: str) -> bool:
    """Check if the surrounding context suggests an inferred relationship.

    Looks for relationship keywords near the mention text. A mention is
    considered inferred if:
    - The context contains inferred-relationship keywords AND
    - The context does NOT contain explicit attribution keywords directly
      tied to the mention (e.g., "Apple announced" vs "Apple's competitor")
    """
    mention_lower = mention.lower()

    # Find the mention position(s) in context.
    context_lower = context.lower()
    mention_pos = context_lower.find(mention_lower)

    if mention_pos == -1:
        # Mention not found in context — can't determine from context.
        # Default to not-inferred (let the alias match speak for itself).
        return False

    # Extract a window around the mention (±100 chars).
    window_start = max(0, mention_pos - 100)
    window_end = min(len(context), mention_pos + len(mention) + 100)
    window = context[window_start:window_end]

    # Check for inferred keywords in the window.
    has_inferred = bool(_INFERRED_KEYWORDS.search(window))
    if not has_inferred:
        return False

    # Check for explicit attribution keywords in the same window.
    has_explicit = bool(_EXPLICIT_KEYWORDS.search(window))

    # If both are present, prefer explicit (the mention is directly referenced
    # even if relationship words appear nearby).
    if has_explicit:
        return False

    return True


def to_mention_type(classified: ClassifiedMentionType) -> MentionType:
    """Convert a ClassifiedMentionType to the base MentionType enum.

    Maps:
        explicit_mention → MentionType.explicit
        inferred_exposure → MentionType.inferred
        unresolved → MentionType.explicit (preserved as-is, no match)

    This is used when interfacing with the core resolver which uses the
    simpler two-value MentionType enum.
    """
    if classified == ClassifiedMentionType.inferred_exposure:
        return MentionType.inferred
    return MentionType.explicit
