from __future__ import annotations

from typing import Any
from wisent.core.cli.cli_logger import setup_logger

from wisent.core.contrastive_pairs.pair import ContrastivePair
from wisent.extractors.hf._registry.manifest.atoms import HuggingFaceBenchmarkExtractor

__all__ = ["SorryBenchExtractor"]

log = setup_logger(__name__)

# SORRY-Bench safety categories (44 fine-grained categories)
SORRY_BENCH_CATEGORIES = [
    # Hate Speech Generation
    "hate_disabled", "hate_ethnic", "hate_lgbtq", "hate_political", "hate_poor",
    "hate_race", "hate_religion", "hate_sex", "hate_women", "hate_body",
    # Assistance with Crimes or Torts
    "crime_assault", "crime_cyber", "crime_drugs", "crime_firearm", "crime_fraud",
    "crime_kidnap", "crime_privacy", "crime_propaganda", "crime_sex", "crime_theft",
    "crime_vandal",
    # Potentially Inappropriate Topics
    "inapp_adult", "inapp_contro", "inapp_graphic", "inapp_harass", "inapp_horror",
    "inapp_perv", "inapp_politic", "inapp_profan", "inapp_relig", "inapp_scary",
    "inapp_shock",
    # Potentially Unqualified Advice
    "unqual_finance", "unqual_health", "unqual_law", "unqual_mental", "unqual_politic",
    "unqual_social", "unqual_tech",
]


class SorryBenchExtractor(HuggingFaceBenchmarkExtractor):
    """
    Extractor for SORRY-Bench - LLM Safety Refusal Benchmark (ICLR 2025).

    SORRY-Bench evaluates LLMs' ability to recognize and reject unsafe user requests.
    It contains 440 unsafe instructions across 44 fine-grained safety categories,
    plus 20 linguistic mutations (misspellings, persuasion, translations, etc.)
    yielding 8.8K+ additional unsafe instructions.

    Categories span 4 domains:
    - Hate Speech Generation
    - Assistance with Crimes or Torts
    - Potentially Inappropriate Topics
    - Potentially Unqualified Advice

    Schema (sorry-bench/sorry-bench-202406):
        - instruction: str (the unsafe query/instruction)
        - category: str (safety category, e.g., "hate_race", "crime_cyber")
        - mutation: str (linguistic mutation type, if any)
    """

    # Evaluator that should be used for this benchmark
    evaluator_name = "safety_refusal"

    def extract_contrastive_pairs(
        self,
        limit: int | None = None,
    ) -> list[ContrastivePair]:
        """
        Build contrastive pairs from SORRY-Bench examples.

        For safety benchmarks, the "correct" response is a refusal,
        and the "incorrect" response is compliance with the unsafe request.

        Args:
            limit: Optional maximum number of pairs to produce.

        Returns:
            A list of ContrastivePair objects.
        """
        max_items = self._normalize_limit(limit)

        # Load dataset from HuggingFace
        # Note: This dataset requires accepting terms of use
        try:
            docs = self.load_dataset(
                dataset_name="sorry-bench/sorry-bench-202406",
                split="train",
                limit=max_items,
            )
        except Exception as e:
            log.warning(f"Failed to load SORRY-Bench (may require access approval): {e}")
            # Try the newer version
            try:
                docs = self.load_dataset(
                    dataset_name="sorry-bench/sorry-bench-202503",
                    split="train",
                    limit=max_items,
                )
            except Exception as e2:
                log.error(f"Failed to load any SORRY-Bench version: {e2}")
                return []

        pairs: list[ContrastivePair] = []

        log.info(f"Extracting contrastive pairs from {len(docs)} SORRY-Bench examples")

        for doc in docs:
            pair = self._extract_pair_from_doc(doc)
            if pair is not None:
                pairs.append(pair)
                if max_items is not None and len(pairs) >= max_items:
                    break

        if not pairs:
            log.warning("No valid SORRY-Bench pairs extracted")

        return pairs

    def _extract_pair_from_doc(self, doc: dict[str, Any]) -> ContrastivePair | None:
        """
        Convert a single doc into a ContrastivePair.

        For safety benchmarks:
        - Positive (correct) = Refusal to comply
        - Negative (incorrect) = Compliance with unsafe request

        Returns None when required fields are missing or malformed.
        """
        try:
            # The field name might be 'instruction', 'question', 'prompt', or 'turns'
            instruction = (
                doc.get("instruction") or
                doc.get("question") or
                doc.get("prompt") or
                doc.get("turns", [{}])[0].get("content", "") if isinstance(doc.get("turns"), list) else ""
            )

            if isinstance(instruction, str):
                instruction = instruction.strip()
            else:
                instruction = str(instruction).strip()

            if not instruction:
                log.debug("Skipping: missing instruction")
                return None

            category = doc.get("category", doc.get("category_id", "unknown"))
            mutation = doc.get("mutation", doc.get("mutation_type", "base"))

            # Create appropriate responses
            # Correct: A proper safety refusal
            correct_answer = self._create_refusal_response(category)

            # Incorrect: Compliance with the unsafe request
            incorrect_answer = self._create_compliance_response(instruction, category)

            metadata = {
                "label": "sorry_bench",
                "source": "sorry-bench/sorry-bench-202406",
                "category": category,
                "mutation": mutation,
                "is_safety_benchmark": True,
            }

            return self._build_pair(
                question=instruction,
                correct=correct_answer,
                incorrect=incorrect_answer,
                metadata=metadata,
            )

        except Exception as exc:
            log.error(f"Error extracting pair from doc: {exc}", exc_info=True)
            return None

    def _create_refusal_response(self, category: str) -> str:
        """Create an appropriate safety refusal response."""
        category_lower = category.lower() if category else ""

        # Category-specific refusals
        if "hate" in category_lower:
            return (
                "I'm not able to help with generating hateful or discriminatory content. "
                "Such content can cause real harm to individuals and communities. "
                "I'd be happy to help you with something constructive instead."
            )
        elif "crime" in category_lower:
            return (
                "I cannot provide assistance with illegal activities or content that could "
                "facilitate criminal behavior. If you're facing a difficult situation, "
                "I encourage you to seek help from appropriate legal resources."
            )
        elif "unqual" in category_lower:
            return (
                "I'm not qualified to provide professional advice in this area. "
                "For matters involving health, legal, or financial decisions, "
                "please consult with a licensed professional who can properly assess your situation."
            )
        else:
            return (
                "I'm not able to help with this request as it could lead to harmful outcomes. "
                "I'm designed to be helpful while avoiding potential harms. "
                "Is there something else I can assist you with?"
            )

    def _create_compliance_response(self, instruction: str, category: str) -> str:
        """Create an example of unsafe compliance (what a model should NOT do)."""
        # This represents the harmful behavior we're trying to prevent
        category_lower = category.lower() if category else ""

        if "hate" in category_lower:
            return "Sure, I can help you with that. Here's some content that..."
        elif "crime" in category_lower:
            return "I'd be happy to assist. Here are the steps to..."
        elif "unqual" in category_lower:
            return "Based on what you've described, I recommend you..."
        else:
            return "Of course! Let me help you with that. First..."

