"""LLM Integration Module for Quran validation.

Provides tools for integrating Quran validation into LLM pipelines:
1. System prompts that instruct LLMs to tag Quran quotes
2. Post-processors that validate and correct tagged quotes
3. Scanners that detect untagged potential Quran content
"""

from __future__ import annotations

import re
from typing import Literal

from .normalizer import normalize_arabic
from .types import (
    FabricationAnalysis,
    LLMProcessorOptions,
    ProcessedOutput,
    QuoteAnalysis,
)
from .validator import QuranValidator


# --- System Prompts ---

SYSTEM_PROMPTS: dict[str, str] = {
    "xml": (
        'When quoting verses from the Quran, you MUST use this exact format:\n'
        '<quran ref="SURAH:AYAH">ARABIC_TEXT</quran>\n\n'
        'For multiple consecutive verses, use a range:\n'
        '<quran ref="SURAH:START-END">ARABIC_TEXT</quran>\n\n'
        'Examples:\n'
        '<quran ref="1:1">بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ</quran>\n'
        '<quran ref="112:1-4">قُلْ هُوَ ٱللَّهُ أَحَدٌ ٱللَّهُ ٱلصَّمَدُ لَمْ يَلِدْ وَلَمْ يُولَدْ وَلَمْ يَكُن لَّهُۥ كُفُوًا أَحَدٌۢ</quran>\n\n'
        'Rules:\n'
        '- Always include the reference (surah:ayah or surah:start-end for ranges)\n'
        '- Use the exact Arabic text with full diacritics if possible\n'
        '- Never paraphrase or partially quote without indication\n'
        '- If unsure of exact wording, say "approximately" before the quote'
    ),
    "markdown": (
        'When quoting verses from the Quran, use this format:\n'
        '```quran ref="SURAH:AYAH"\nARABIC_TEXT\n```\n\n'
        'For verse ranges, use:\n'
        '```quran ref="SURAH:START-END"\nARABIC_TEXT\n```\n\n'
        'Example:\n'
        '```quran ref="112:1-4"\n'
        'قُلْ هُوَ ٱللَّهُ أَحَدٌ ٱللَّهُ ٱلصَّمَدُ لَمْ يَلِدْ وَلَمْ يُولَدْ وَلَمْ يَكُن لَّهُۥ كُفُوًا أَحَدٌۢ\n'
        '```'
    ),
    "bracket": (
        'When quoting Quran verses, use: [[Q:SURAH:AYAH|ARABIC_TEXT]]\n'
        'For verse ranges: [[Q:SURAH:START-END|ARABIC_TEXT]]\n\n'
        'Example: [[Q:1:1|بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ]]\n'
        'Example range: [[Q:112:1-4|قُلْ هُوَ ٱللَّهُ أَحَدٌ ٱللَّهُ ٱلصَّمَدُ لَمْ يَلِدْ وَلَمْ يُولَدْ وَلَمْ يَكُن لَّهُۥ كُفُوًا أَحَدٌۢ]]'
    ),
    "minimal": (
        'Always cite Quran verses with their reference number in parentheses '
        'immediately after, like: "بِسْمِ ٱللَّهِ ٱلرَّحْمَٰنِ ٱلرَّحِيمِ (1:1)" '
        'or for ranges "... (112:1-4)"'
    ),
}


# --- Contextual patterns ---

_QURAN_CONTEXT_PATTERNS = [
    re.compile(
        r"(?:Allah\s+says?|God\s+says?|the\s+Quran\s+says?|in\s+the\s+Quran|"
        r"Quranic\s+verse|verse\s+states?|ayah|ayat|surah)\s*[:\-]?\s*",
        re.IGNORECASE,
    ),
    re.compile(
        r"(?:قال\s+الله|قال\s+تعالى|يقول\s+الله|في\s+القرآن|الآية|سورة)\s*[:\-]?\s*"
    ),
    re.compile(r"\(?\d{1,3}:\d{1,3}(?:-\d{1,3})?\)?"),
    re.compile(r"\[[\w\-]+:\d+(?:-\d+)?\]"),
]


# --- Tag patterns ---

_TAG_PATTERNS: dict[str, re.Pattern[str]] = {
    "xml": re.compile(r'<quran\s+ref=["\'](\d+:\d+(?:-\d+)?)["\']>([\s\S]*?)</quran>', re.IGNORECASE),
    "markdown": re.compile(r'```quran\s+ref=["\'](\d+:\d+(?:-\d+)?)["\'][\r\n]([\s\S]*?)[\r\n]```', re.IGNORECASE),
    "bracket": re.compile(r'\[\[Q:(\d+:\d+(?:-\d+)?)\|([\s\S]*?)\]\]', re.IGNORECASE),
}

_INLINE_REF = re.compile(
    r"([\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]"
    r"[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\s]*)"
    r"\s*\((\d+:\d+(?:-\d+)?)\)"
)

_ARABIC_AFTER_CONTEXT = re.compile(
    r"^[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\s]+"
)

_ARABIC_SEGMENT_PATTERN = re.compile(
    r"[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]"
    r"[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF\s]*"
)


def _parse_reference(ref: str) -> dict | None:
    """Parse a verse reference like '1:1' or '2:255-257'."""
    m = re.match(r"^(\d+):(\d+)(?:-(\d+))?$", ref)
    if not m:
        return None
    surah = int(m.group(1))
    start_ayah = int(m.group(2))
    end_ayah = int(m.group(3)) if m.group(3) else None
    return {
        "surah": surah,
        "start_ayah": start_ayah,
        "end_ayah": end_ayah,
        "is_range": end_ayah is not None,
    }


class LLMProcessor:
    """LLM Output Processor for Quran validation.

    Processes LLM-generated text to validate and correct Quran quotes.

    Example::

        processor = LLMProcessor()
        result = processor.process(llm_response)

        if not result.all_valid:
            print("Some quotes need attention")

        print(result.corrected_text)
    """

    def __init__(self, options: LLMProcessorOptions | None = None):
        opts = options or LLMProcessorOptions()
        self._validator = QuranValidator()
        self._auto_correct = opts.auto_correct
        self._scan_untagged = opts.scan_untagged
        self._tag_format = opts.tag_format

    def get_system_prompt(self) -> str:
        """Get the recommended system prompt for the configured tag format."""
        return SYSTEM_PROMPTS[self._tag_format]

    def process(self, text: str) -> ProcessedOutput:
        """Process LLM output to validate and optionally correct Quran quotes.

        Args:
            text: The LLM-generated text.

        Returns:
            Processed output with validation results.
        """
        quotes: list[QuoteAnalysis] = []
        warnings: list[str] = []
        corrected_text = text

        # Step 1: Extract and validate tagged quotes
        tagged_quotes = self._extract_tagged_quotes(text)
        for tagged in tagged_quotes:
            analysis = self._analyze_quote(
                tagged["text"],
                tagged["reference"],
                tagged["start_index"],
                tagged["end_index"],
                "tagged",
            )
            quotes.append(analysis)

            if self._auto_correct and analysis.was_corrected:
                corrected_text = corrected_text.replace(
                    tagged["full_match"],
                    self._format_corrected_tag(analysis),
                    1,
                )

        # Step 2: Scan for contextual quotes
        contextual_quotes = self._extract_contextual_quotes(text, tagged_quotes)
        for ctx in contextual_quotes:
            analysis = self._analyze_quote(
                ctx["text"], None, ctx["start_index"], ctx["end_index"], "contextual"
            )
            if analysis.is_valid:
                quotes.append(analysis)
                if self._auto_correct and analysis.was_corrected:
                    corrected_text = corrected_text.replace(ctx["text"], analysis.corrected, 1)

        # Step 3: Scan for untagged Arabic
        if self._scan_untagged:
            untagged_quotes = self._scan_untagged_arabic(text, quotes)
            for untagged in untagged_quotes:
                analysis = self._analyze_quote(
                    untagged["text"],
                    None,
                    untagged["start_index"],
                    untagged["end_index"],
                    "fuzzy",
                )
                if analysis.is_valid:
                    quotes.append(analysis)
                    warnings.append(
                        f'Untagged Quran quote detected: "{untagged["text"][:50]}..." '
                        f"({analysis.reference})"
                    )
                    if self._auto_correct and analysis.was_corrected:
                        corrected_text = corrected_text.replace(
                            untagged["text"], analysis.corrected, 1
                        )

        all_valid = all(q.is_valid and not q.was_corrected for q in quotes)

        return ProcessedOutput(
            corrected_text=corrected_text,
            all_valid=all_valid,
            quotes=quotes,
            warnings=warnings,
        )

    def validate_quote(
        self, text: str, expected_ref: str | None = None
    ) -> dict:
        """Validate a single quote without full processing."""
        validation = self._validator.validate(text)

        if not validation.is_valid:
            return {"is_valid": False}

        if expected_ref and validation.reference != expected_ref:
            return {
                "is_valid": False,
                "correct_text": validation.matched_verse.text if validation.matched_verse else None,
                "actual_ref": validation.reference,
            }

        needs_correction = (
            validation.match_type != "exact" and validation.matched_verse is not None
        )
        return {
            "is_valid": True,
            "correct_text": validation.matched_verse.text if needs_correction and validation.matched_verse else None,
            "actual_ref": validation.reference,
        }

    # --- Private methods ---

    def _extract_tagged_quotes(self, text: str) -> list[dict]:
        results: list[dict] = []

        pattern = _TAG_PATTERNS[self._tag_format]
        for match in pattern.finditer(text):
            results.append({
                "reference": match.group(1),
                "text": match.group(2).strip(),
                "start_index": match.start(),
                "end_index": match.end(),
                "full_match": match.group(0),
            })

        # Also check for inline references
        for match in _INLINE_REF.finditer(text):
            # Skip if overlaps with already found
            overlaps = any(
                match.start() >= r["start_index"] and match.start() < r["end_index"]
                for r in results
            )
            if not overlaps:
                inline_text = match.group(1).strip()
                if not inline_text:
                    continue
                results.append({
                    "text": inline_text,
                    "reference": match.group(2),
                    "start_index": match.start(),
                    "end_index": match.end(),
                    "full_match": match.group(0),
                })

        return results

    def _extract_contextual_quotes(
        self, text: str, already_found: list[dict]
    ) -> list[dict]:
        results: list[dict] = []

        for pattern in _QURAN_CONTEXT_PATTERNS:
            for match in pattern.finditer(text):
                after_match = text[match.end():]
                arabic_match = _ARABIC_AFTER_CONTEXT.match(after_match)

                if arabic_match and len(arabic_match.group(0).strip()) >= 10:
                    start_index = match.end()
                    end_index = start_index + len(arabic_match.group(0))

                    overlaps = any(
                        (start_index >= r["start_index"] and start_index < r["end_index"])
                        or (end_index > r["start_index"] and end_index <= r["end_index"])
                        for r in already_found
                    )
                    if not overlaps:
                        results.append({
                            "text": arabic_match.group(0).strip(),
                            "start_index": start_index,
                            "end_index": end_index,
                        })

        return results

    def _scan_untagged_arabic(
        self, text: str, already_found: list
    ) -> list[dict]:
        results: list[dict] = []

        for match in _ARABIC_SEGMENT_PATTERN.finditer(text):
            segment = match.group(0).strip()
            if len(segment) < 15:
                continue

            overlaps = any(
                (match.start() >= r.start_index and match.start() < r.end_index)
                or (match.end() > r.start_index and match.end() <= r.end_index)
                for r in already_found
                if hasattr(r, "start_index")
            )
            if not overlaps:
                results.append({
                    "text": segment,
                    "start_index": match.start(),
                    "end_index": match.end(),
                })

        return results

    def _analyze_quote(
        self,
        text: str,
        expected_ref: str | None,
        start_index: int,
        end_index: int,
        detection_method: Literal["tagged", "contextual", "fuzzy"],
    ) -> QuoteAnalysis:
        normalized_input = normalize_arabic(text)

        # Handle expected reference
        if expected_ref:
            parsed = _parse_reference(expected_ref)
            if parsed and parsed["is_range"] and parsed["end_ayah"]:
                return self._analyze_range_quote(
                    text, expected_ref, parsed, start_index, end_index, detection_method
                )

            if parsed:
                expected_verse = self._validator.get_verse(parsed["surah"], parsed["start_ayah"])
                if expected_verse:
                    normalized_expected = normalize_arabic(expected_verse.text)
                    is_exact = text.strip() == expected_verse.text
                    is_normalized_match = normalized_input == normalized_expected

                    if is_exact:
                        return QuoteAnalysis(
                            original=text, corrected=text, is_valid=True,
                            reference=expected_ref, detection_method=detection_method,
                            start_index=start_index, end_index=end_index,
                            was_corrected=False, normalized_input=normalized_input,
                        )

                    if is_normalized_match:
                        return QuoteAnalysis(
                            original=text, corrected=expected_verse.text, is_valid=True,
                            reference=expected_ref, detection_method=detection_method,
                            start_index=start_index, end_index=end_index,
                            was_corrected=True, normalized_input=normalized_input,
                        )

                    # Check if it's real Quran from a different verse
                    global_validation = self._validator.validate(text)
                    if global_validation.is_valid and global_validation.reference:
                        return QuoteAnalysis(
                            original=text,
                            corrected=global_validation.matched_verse.text if global_validation.matched_verse else text,
                            is_valid=True,
                            reference=global_validation.reference,
                            detection_method=detection_method,
                            start_index=start_index, end_index=end_index,
                            was_corrected=True, normalized_input=normalized_input,
                        )

                    # Invalid
                    return QuoteAnalysis(
                        original=text, corrected=text, is_valid=False,
                        reference=expected_ref, detection_method=detection_method,
                        start_index=start_index, end_index=end_index,
                        was_corrected=False, normalized_input=normalized_input,
                        expected_normalized=normalized_expected,
                        fabrication_analysis=self._validator.analyze_fabrication(text),
                    )

                # Non-existent verse reference — search globally
                global_validation = self._validator.validate(text)
                if global_validation.is_valid and global_validation.reference:
                    return QuoteAnalysis(
                        original=text,
                        corrected=global_validation.matched_verse.text if global_validation.matched_verse else text,
                        is_valid=True,
                        reference=global_validation.reference,
                        detection_method=detection_method,
                        start_index=start_index, end_index=end_index,
                        was_corrected=True, normalized_input=normalized_input,
                    )

                return QuoteAnalysis(
                    original=text, corrected=text, is_valid=False,
                    reference=expected_ref, detection_method=detection_method,
                    start_index=start_index, end_index=end_index,
                    was_corrected=False, normalized_input=normalized_input,
                    fabrication_analysis=self._validator.analyze_fabrication(text),
                )

        # No expected reference — search globally
        validation = self._validator.validate(text)
        was_corrected = False
        corrected = text

        if validation.is_valid and validation.match_type != "exact" and validation.matched_verse:
            corrected = validation.matched_verse.text
            was_corrected = True

        result = QuoteAnalysis(
            original=text,
            corrected=corrected,
            is_valid=validation.is_valid,
            reference=validation.reference,
            detection_method=detection_method,
            start_index=start_index,
            end_index=end_index,
            was_corrected=was_corrected,
            normalized_input=normalized_input,
        )

        if not validation.is_valid:
            result.fabrication_analysis = self._validator.analyze_fabrication(text)

        return result

    def _analyze_range_quote(
        self,
        text: str,
        expected_ref: str,
        parsed: dict,
        start_index: int,
        end_index: int,
        detection_method: Literal["tagged", "contextual", "fuzzy"],
    ) -> QuoteAnalysis:
        normalized_input = normalize_arabic(text)
        vrange = self._validator.get_verse_range(
            parsed["surah"], parsed["start_ayah"], parsed["end_ayah"]
        )

        if not vrange:
            return QuoteAnalysis(
                original=text, corrected=text, is_valid=False,
                reference=expected_ref, detection_method=detection_method,
                start_index=start_index, end_index=end_index,
                was_corrected=False, normalized_input=normalized_input,
                fabrication_analysis=self._validator.analyze_fabrication(text),
            )

        normalized_range = normalize_arabic(vrange["text"])
        is_exact = text.strip() == vrange["text"]
        is_normalized_match = normalized_input == normalized_range

        if is_exact:
            return QuoteAnalysis(
                original=text, corrected=text, is_valid=True,
                reference=expected_ref, detection_method=detection_method,
                start_index=start_index, end_index=end_index,
                was_corrected=False, normalized_input=normalized_input,
            )

        if is_normalized_match:
            return QuoteAnalysis(
                original=text, corrected=vrange["text"], is_valid=True,
                reference=expected_ref, detection_method=detection_method,
                start_index=start_index, end_index=end_index,
                was_corrected=True, normalized_input=normalized_input,
            )

        return QuoteAnalysis(
            original=text, corrected=text, is_valid=False,
            reference=expected_ref, detection_method=detection_method,
            start_index=start_index, end_index=end_index,
            was_corrected=False, normalized_input=normalized_input,
            expected_normalized=normalized_range,
            fabrication_analysis=self._validator.analyze_fabrication(text),
        )

    def _format_corrected_tag(self, analysis: QuoteAnalysis) -> str:
        if self._tag_format == "xml":
            return f'<quran ref="{analysis.reference}">{analysis.corrected}</quran>'
        elif self._tag_format == "markdown":
            return f'```quran ref="{analysis.reference}"\n{analysis.corrected}\n```'
        elif self._tag_format == "bracket":
            return f"[[Q:{analysis.reference}|{analysis.corrected}]]"
        else:
            return f"{analysis.corrected} ({analysis.reference})"


def create_llm_processor(options: LLMProcessorOptions | None = None) -> LLMProcessor:
    """Create an LLM processor instance."""
    return LLMProcessor(options)


def quick_validate(text: str) -> dict:
    """Quick validation of a complete LLM response.

    Args:
        text: LLM output to validate.

    Returns:
        Dict with 'has_quran_content', 'all_valid', and 'issues' keys.
    """
    processor = LLMProcessor(LLMProcessorOptions(auto_correct=False))
    result = processor.process(text)

    issues: list[str] = []
    for q in result.quotes:
        if not q.is_valid or q.was_corrected:
            status = "imprecise" if q.is_valid else "invalid"
            msg = f'Quote "{q.original[:30]}..." is {status}'
            if q.reference:
                msg += f" (should be {q.reference})"
            issues.append(msg)
    issues.extend(result.warnings)

    return {
        "has_quran_content": len(result.quotes) > 0,
        "all_valid": result.all_valid,
        "issues": issues,
    }
