"""Extraction pipeline: convert conversation segments into structured observations."""
from __future__ import annotations

import re
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from greymatter_core.llm_gateway import OllamaLLM


class ExtractionPipeline:
    """Extract observations from conversation text using heuristic rules."""

    DECISION_PATTERNS = [
        r"(?i)decided to\b",
        r"(?i)let's go with\b",
        r"(?i)we'll use\b",
        r"(?i)approved\b",
        r"(?i)agreed on\b",
        r"(?i)the approach is\b",
    ]

    CORRECTION_PATTERNS = [
        r"(?i)actually,?\s+(?:it should|that's wrong|no,?\s+)",
        r"(?i)fix(?:ed)?\b.*\b(?:bug|issue|error|mistake)",
        r"(?i)(?:should|must) (?:be|have been)\b",
        r"(?i)correction:\s*",
        r"(?i)that's (?:not right|incorrect|wrong)",
    ]

    PATTERN_PATTERNS = [
        r"(?i)(?:I|we) (?:always|never|usually|typically)\b",
        r"(?i)(?:pattern|convention|standard):\s*",
        r"(?i)(?:best practice|rule of thumb)\b",
        r"(?i)(?:remember to|don't forget to|make sure to)\b",
    ]

    INSIGHT_PATTERNS = [
        r"(?i)(?:I (?:realized|noticed|discovered|found))\b",
        r"(?i)(?:turns out|it appears|interesting(?:ly)?)\b",
        r"(?i)(?:key (?:insight|learning|takeaway))\b",
        r"(?i)(?:the root cause (?:is|was))\b",
    ]

    PREFERENCE_PATTERNS = [
        r"(?i)(?:I want|I need|I prefer|we need)\b",
        r"(?i)(?:can't afford|won't accept|must have)\b",
    ]

    BUSINESS_PATTERNS = [
        r"(?i)(?:pricing|license|revenue|customer|market)\b",
        r"(?i)(?:free tier|pro tier|enterprise)\b",
        r"(?i)(?:go-to-market|launch|positioning)\b",
    ]

    TRADEOFF_PATTERNS = [
        r"(?i)(?:trade-?off|instead of|rather than|versus|vs\.?)\b",
        r"(?i)(?:pros? and cons?|advantages? (?:and|vs) disadvantages?)\b",
    ]

    @classmethod
    def extract_heuristic(
        cls, text: str, session_id: str = "", trigger: str = "manual"
    ) -> list[dict[str, Any]]:
        """Extract observations from text using pattern matching."""
        observations = []
        sentences = cls._split_sentences(text)

        for sentence in sentences:
            category = cls._classify(sentence)
            if category:
                title = cls._generate_title(sentence, category)
                observations.append({
                    "session_id": session_id,
                    "trigger": trigger,
                    "category": category,
                    "title": title,
                    "content": sentence.strip(),
                    "confidence": 0.4,
                    "domains": "",
                })

        return observations

    @classmethod
    def _classify(cls, text: str) -> str | None:
        """Classify a text segment into an observation category."""
        for pattern in cls.DECISION_PATTERNS:
            if re.search(pattern, text):
                return "decision"
        for pattern in cls.CORRECTION_PATTERNS:
            if re.search(pattern, text):
                return "correction"
        for pattern in cls.PATTERN_PATTERNS:
            if re.search(pattern, text):
                return "pattern"
        for pattern in cls.INSIGHT_PATTERNS:
            if re.search(pattern, text):
                return "insight"
        for pattern in cls.PREFERENCE_PATTERNS:
            if re.search(pattern, text):
                return "pattern"
        for pattern in cls.BUSINESS_PATTERNS:
            if re.search(pattern, text):
                return "decision"
        for pattern in cls.TRADEOFF_PATTERNS:
            if re.search(pattern, text):
                return "insight"
        return None

    @classmethod
    def _split_sentences(cls, text: str) -> list[str]:
        """Split text into sentence-like segments."""
        segments = re.split(r'(?<=[.!?])\s+|\n+', text)
        return [s.strip() for s in segments if s.strip() and len(s.strip()) > 20]

    @classmethod
    def _generate_title(cls, text: str, category: str) -> str:
        """Generate a short title from observation content."""
        title = text[:100].strip()
        if len(text) > 100:
            last_space = title.rfind(" ")
            if last_space > 50:
                title = title[:last_space]
            title += "..."
        return f"[{category}] {title}"

    # ---- LLM-powered extraction ----

    _VALID_CATEGORIES = frozenset({
        "decision", "correction", "pattern", "insight", "reference", "gotcha",
    })

    _SYSTEM_PROMPT = (
        "You are an observation extraction engine. Analyze the conversation "
        "text and extract structured observations.\n\n"
        "For each observation, return a JSON object with:\n"
        "- category: one of \"decision\", \"correction\", \"pattern\", "
        "\"insight\", \"reference\", \"gotcha\"\n"
        "- title: a concise 5-10 word title (do NOT include [category] prefix)\n"
        "- content: the full observation text (1-3 sentences)\n"
        "- confidence: float 0.0-1.0 (how certain is this a real observation)\n"
        "- domains: array of relevant domain tags (e.g. [\"database\", \"security\", "
        "\"architecture\", \"testing\", \"deployment\", \"business\", \"performance\"])\n\n"
        "Return a JSON array of observations. If none found, return [].\n\n"
        "Categories:\n"
        "- decision: a choice, direction, or business strategy was established\n"
        "- correction: something was wrong and fixed\n"
        "- pattern: a recurring practice, preference, or convention\n"
        "- insight: a discovery, realization, or trade-off analysis\n"
        "- reference: factual information worth remembering\n"
        "- gotcha: a pitfall or trap to avoid\n\n"
        "Domain tagging: assign 1-3 domain tags per observation from the conversation context. "
        "Use lowercase, concise tags."
    )

    @classmethod
    def extract_llm(
        cls,
        text: str,
        llm: OllamaLLM,
        session_id: str = "",
        trigger: str = "manual",
    ) -> list[dict[str, Any]]:
        """Extract observations using LLM for higher quality analysis.

        Falls back to extract_heuristic() if LLM is unavailable or fails.
        """
        user_prompt = f"Extract observations from this text:\n\n{text[:8000]}"

        try:
            results = llm.generate_json(user_prompt, system=cls._SYSTEM_PROMPT)
            if not isinstance(results, list):
                return cls.extract_heuristic(text, session_id, trigger)

            observations: list[dict[str, Any]] = []
            for r in results:
                category = r.get("category", "")
                if category not in cls._VALID_CATEGORIES:
                    continue
                raw_conf = r.get("confidence", 0.5)
                raw_domains = r.get("domains", [])
                if isinstance(raw_domains, list):
                    domains_str = ",".join(raw_domains)
                else:
                    domains_str = str(raw_domains) if raw_domains else ""
                observations.append({
                    "session_id": session_id,
                    "trigger": trigger,
                    "category": category,
                    "title": f"[{category}] {r.get('title', 'Untitled')}",
                    "content": r.get("content", ""),
                    "confidence": min(max(float(raw_conf), 0.0), 1.0),
                    "domains": domains_str,
                })
            return observations if observations else cls.extract_heuristic(text, session_id, trigger)
        except Exception:
            return cls.extract_heuristic(text, session_id, trigger)

    @classmethod
    def extract(
        cls,
        text: str,
        session_id: str = "",
        trigger: str = "manual",
        llm: OllamaLLM | None = None,
    ) -> list[dict[str, Any]]:
        """Smart extraction: use LLM if available, fall back to heuristics."""
        if llm is not None:
            return cls.extract_llm(text, llm, session_id, trigger)
        return cls.extract_heuristic(text, session_id, trigger)
