"""Common utilities for RLM benchmarks.

Extracted to reduce duplication between factual_extraction.py and semantic_extraction.py.
"""

from __future__ import annotations

import json
import os
import re
import subprocess
import sys
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    pass

# Default database path
DEFAULT_DB = Path.home() / "Library/Application Support/RecallRLM/shadow_corpus_test.db"


def get_rlm_path() -> Path:
    """Get path to the RLM tool directory.

    Returns:
        Path to the rcl-rlm directory
    """
    repo_root = Path(__file__).parent.parent.parent.parent
    return repo_root / "tools" / "rcl-rlm"


def run_rlm_query(
    question: str,
    db_path: str,
    verbose: bool = False,
    benchmark_mode: bool = False,
    tips_profile: str | None = None,
    policy_hints: str | None = None,
) -> tuple[str, int, int]:
    """Run an RLM query and return the answer, subcall count, and token usage.

    Args:
        question: The question to ask RLM
        db_path: Path to the shadow.db database
        verbose: Whether to print verbose output
        benchmark_mode: Whether to request JSON output with subcall count

    Returns:
        Tuple of (answer_text, subcalls_made, total_tokens)
    """
    rlm_path = get_rlm_path()

    resolved_tips_profile = tips_profile or os.environ.get("RECALL_RLM_TIPS_PROFILE", "full")

    cmd = [
        "uv", "run",
        "--directory", str(rlm_path),
        "python", "-m", "rcl_rlm.cli",
        question,
        "--db", db_path,
        "--tips-profile", resolved_tips_profile,
    ]

    if policy_hints:
        cmd.extend(["--policy-hints", policy_hints])

    if benchmark_mode:
        cmd.append("--benchmark")

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        cwd=str(rlm_path),
    )

    output = result.stdout
    subcalls = 0
    tokens = 0

    # Try to parse JSON output for subcall count and tokens
    if benchmark_mode:
        try:
            data = json.loads(output)
            subcalls = data.get("sub_calls_made", 0)
            tokens = data.get("total_tokens", 0)
            output = data.get("answer", output)
        except json.JSONDecodeError:
            # Not JSON, look for subcall count in stderr
            match = re.search(r'Sub-calls made:\s*(\d+)', result.stderr)
            if match:
                subcalls = int(match.group(1))

    if verbose:
        print(f"\n  Output: {output[:300]}...", file=sys.stderr)
        print(f"  Sub-calls: {subcalls}, Tokens: {tokens}", file=sys.stderr)

    return output, subcalls, tokens


def extract_number(text: str) -> int | None:
    """Extract the most relevant number from text.

    Prioritizes numbers that appear after key phrases like 'count', 'total', 'found'.
    Falls back to last large number if no context clues.
    """
    # First, look for patterns like "count: 123" or "= 123" or ": 123"
    match = re.search(r'(?:count|total|found|result|answer)[:\s=]+(\d[\d,]*)', text, re.I)
    if match:
        return int(match.group(1).replace(',', ''))

    # Look for patterns in dict output like 'sent_count': 132087
    match = re.search(r"'[^']*count[^']*':\s*(\d+)", text, re.I)
    if match:
        return int(match.group(1))

    # Look for numbers after "is" or "are" (e.g., "There are 123 messages")
    match = re.search(r'(?:is|are|was|were)\s+(\d[\d,]*)', text)
    if match:
        return int(match.group(1).replace(',', ''))

    # Fall back to the last large number (likely the answer, not intermediate output)
    numbers = re.findall(r'\d[\d,]*', text)
    if numbers:
        # Filter to numbers > 10 and take the last one
        large_nums = [int(n.replace(',', '')) for n in numbers if int(n.replace(',', '')) > 10]
        if large_nums:
            return large_nums[-1]
        return int(numbers[-1].replace(',', ''))
    return None


def extract_percentage(text: str) -> float | None:
    """Extract percentage from text."""
    match = re.search(r'([\d.]+)\s*%', text)
    if match:
        return float(match.group(1))
    return None


def extract_name(text: str) -> str | None:
    """Extract a name/sender from text (first quoted or capitalized word)."""
    # Look for quoted strings first
    match = re.search(r'"([^"]+)"', text)
    if match:
        return match.group(1)
    # Look for "is X" or "was X" patterns
    match = re.search(r'(?:is|was|are)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)?)', text)
    if match:
        return match.group(1)
    return None


def compare_count(
    rlm_answer: int | None,
    ground_truth: int,
    tolerance: float = 0.1,
) -> tuple[bool, str]:
    """Compare count with tolerance.

    Args:
        rlm_answer: The extracted answer from RLM
        ground_truth: The expected value from SQL
        tolerance: Acceptable percentage difference (default 10%)

    Returns:
        Tuple of (passed, reason_string)
    """
    if rlm_answer is None:
        return False, "Could not extract number from RLM answer"

    diff = abs(rlm_answer - ground_truth)
    pct_diff = diff / ground_truth if ground_truth > 0 else 0

    if pct_diff <= tolerance:
        return True, f"RLM: {rlm_answer}, Truth: {ground_truth} (within {tolerance*100}%)"
    else:
        return False, f"RLM: {rlm_answer}, Truth: {ground_truth} (off by {pct_diff*100:.1f}%)"


def compare_exact(rlm_answer: str | None, ground_truth: str) -> tuple[bool, str]:
    """Compare strings exactly (case-insensitive)."""
    if rlm_answer is None:
        return False, "Could not extract answer from RLM response"

    if rlm_answer.lower().strip() == ground_truth.lower().strip():
        return True, f"Match: {rlm_answer}"
    else:
        return False, f"RLM: {rlm_answer}, Truth: {ground_truth}"


def compare_contains(rlm_answer: str, ground_truth: str) -> tuple[bool, str]:
    """Check if ground truth appears in RLM answer."""
    if ground_truth.lower() in rlm_answer.lower():
        return True, f"Found '{ground_truth}' in response"
    else:
        return False, f"'{ground_truth}' not found in response"
