"""
ChatCoVFeatureExtractor — Extract 4 risk features from Chain-of-Verification text.
===================================================================================
Used by ChatGuard to extend the 12-feature native scorer with CoV-derived signals
when mode="synthetic_trace" or "hybrid" and fit(cov_features_list=...) was called.

Features
--------
  contradiction_score   count(contradiction keywords) / max(sentence_count, 1)
  final_confidence      parse "Step 4" for HIGH→0.10 / MEDIUM→0.50 / LOW→0.90
  claim_count_norm      log1p(numbered/bulleted claims) / 3.0
  evidence_gap_ratio    count(gap keywords) / max(sentence_count, 1)

Usage
-----
    from llm_guard.multi_turn.chat_cov_features import ChatCoVFeatureExtractor
    extractor = ChatCoVFeatureExtractor()
    vec = extractor.extract(cov_text)
    arr = vec.to_array()   # shape (4,)
    d   = vec.to_dict()    # for fit(cov_features_list=[d, ...])
Purpose:    Parses CoV traces to extract contradiction score, final confidence, claim count, and evidence gap ratio. Used by ChatGuard to extend the 12-feature native scorer with CoV-derived signals.
Exports:    ChatCoVFeatureExtractor, CoVFeatureVector, COV_FEATURE_KEYS
Added:      v0.27.0
Status:     Experimental
"""
from __future__ import annotations

import math
import re
from dataclasses import dataclass
from typing import List

# ── Keyword sets ──────────────────────────────────────────────────────────────

# Contradiction keywords: plain substrings use str.count; regex uses re.findall
_CONTRADICTION_PLAIN = [
    "no evidence", "unsupported", "incorrect",
    "inaccurate", "wrong", "false",
]
_CONTRADICTION_REGEX = re.compile(r"\bcontradicts?\b")

# Evidence gap keywords (all plain substrings)
_GAP_KEYWORDS = [
    "cannot verify", "no source", "unclear", "unknown",
    "not found", "cannot confirm",
]

_SENTENCE_RE = re.compile(r"[.!?]+")
_CLAIM_RE = re.compile(r"(?:^\s*\d+\.\s|^\s*[-*]\s)", re.MULTILINE)
_CONFIDENCE_RE = re.compile(r"\b(HIGH|MEDIUM|LOW)\b")

_CONFIDENCE_MAP = {"HIGH": 0.10, "MEDIUM": 0.50, "LOW": 0.90}


@dataclass
class CoVFeatureVector:
    contradiction_score: float
    final_confidence: float
    claim_count_norm: float
    evidence_gap_ratio: float

    def to_array(self) -> List[float]:
        return [
            self.contradiction_score,
            self.final_confidence,
            self.claim_count_norm,
            self.evidence_gap_ratio,
        ]

    def to_dict(self) -> dict:
        return {
            "contradiction_score": self.contradiction_score,
            "final_confidence": self.final_confidence,
            "claim_count_norm": self.claim_count_norm,
            "evidence_gap_ratio": self.evidence_gap_ratio,
        }


class ChatCoVFeatureExtractor:
    """Extract CoV risk features from raw Chain-of-Verification text. Thread-safe."""

    def extract(self, cov_text: str) -> CoVFeatureVector:
        if not cov_text.strip():
            return CoVFeatureVector(
                contradiction_score=0.0,
                final_confidence=0.50,
                claim_count_norm=0.0,
                evidence_gap_ratio=0.0,
            )

        text_lower = cov_text.lower()
        sentences = [s.strip() for s in _SENTENCE_RE.split(cov_text) if s.strip()]
        n_sent = max(len(sentences), 1)

        # contradiction_score
        contradiction_count = (
            sum(text_lower.count(kw) for kw in _CONTRADICTION_PLAIN)
            + len(_CONTRADICTION_REGEX.findall(text_lower))
        )
        contradiction_score = min(contradiction_count / n_sent, 1.0)

        # final_confidence — search last 3 sentences for confidence keyword
        last_sentences = " ".join(sentences[-3:])
        matches = _CONFIDENCE_RE.findall(last_sentences)
        final_confidence = _CONFIDENCE_MAP.get(matches[-1], 0.50) if matches else 0.50

        # claim_count_norm — count claims in Step 1 section only (per spec)
        step1_match = re.search(r"Step\s*1.*?(?=Step\s*2|\Z)", cov_text, re.DOTALL | re.IGNORECASE)
        step1_text = step1_match.group(0) if step1_match else cov_text
        claim_count = len(_CLAIM_RE.findall(step1_text))
        claim_count_norm = math.log1p(claim_count) / 3.0

        # evidence_gap_ratio
        gap_count = sum(text_lower.count(kw) for kw in _GAP_KEYWORDS)
        evidence_gap_ratio = min(gap_count / n_sent, 1.0)

        return CoVFeatureVector(
            contradiction_score=float(contradiction_score),
            final_confidence=float(final_confidence),
            claim_count_norm=float(claim_count_norm),
            evidence_gap_ratio=float(evidence_gap_ratio),
        )
