"""BLCE Meeting Notes Processor — regex-based NL extraction.

Extracts dimensions, filters, KPIs, and business rules from
free-text meeting notes using pattern matching.
"""
from __future__ import annotations

import logging
import re
from typing import List

from .contracts import (
    ExtractedDimension,
    ExtractedFilter,
    ExtractedKPI,
    MeetingNotesAnalysis,
)
from .report_patterns import DIMENSION_KEYWORD_MAP, MEASURE_KEYWORD_MAP

logger = logging.getLogger(__name__)


class MeetingNotesProcessor:
    """Extract requirements from free-text meeting notes."""

    # Compiled regex patterns
    _DIM_PATTERN = re.compile(
        r"\b(?:by|per|split\s+by|group\s+by|broken?\s+down\s+by|across)\s+"
        r"([a-z][a-z_]*(?:\s+(?!by\b|per\b|and\b|or\b|from\b|excluding\b)[a-z_]+)?)",
        re.IGNORECASE,
    )
    _FILTER_PATTERN = re.compile(
        r"\b(?:exclud(?:e|ing)|only|where|filter(?:ed|ing)?(?:\s+(?:on|by|for))?|"
        r"not\s+including|without)\s+"
        r"([a-z][a-z_ ]{1,40}?)(?=\s*(?:$|[,;.\n]|\b(?:by|per|and|or|from)\b))",
        re.IGNORECASE,
    )
    _KPI_PATTERN = re.compile(
        r"\b(?:total|sum\s+of|average|avg|count\s+of|net|gross)\s+"
        r"([a-z][a-z_]*(?:\s+[a-z_]+)?)",
        re.IGNORECASE,
    )
    _KPI_RATIO_PATTERN = re.compile(
        r"([a-z][a-z_ ]{1,20})\s+(?:per|\/|divided\s+by)\s+([a-z][a-z_ ]{1,20})",
        re.IGNORECASE,
    )
    _COMPARISON_PATTERN = re.compile(
        r"\b(?:vs\.?|versus|compared\s+to|relative\s+to)\s+([a-z][a-z_ ]{1,30})",
        re.IGNORECASE,
    )
    _DRILL_PATTERN = re.compile(
        r"\b(?:drill\s+down\s+to|roll\s+up\s+to|expand\s+to)\s+"
        r"([a-z][a-z_ ]{1,30})",
        re.IGNORECASE,
    )
    _QUESTION_PATTERN = re.compile(
        r"(?:^|\n)\s*[-*]?\s*((?:what|how|why|when|which|can we|do we|is there)"
        r"[^.?]*\??)",
        re.IGNORECASE,
    )
    _RULE_PATTERN = re.compile(
        r"\b(?:rule|always|never|must|should|require|mandatory):\s*([^\n.]+)",
        re.IGNORECASE,
    )

    def process_notes(
        self, text: str, file_path: str = ""
    ) -> MeetingNotesAnalysis:
        """Process meeting notes text and extract structured requirements.

        Args:
            text: Free-text meeting notes content.
            file_path: Optional source file path.

        Returns:
            MeetingNotesAnalysis with extracted elements.
        """
        dimensions = self._extract_dimension_mentions(text)
        filters = self._extract_filter_mentions(text)
        kpis = self._extract_kpi_mentions(text)
        rules = self._extract_business_rules(text)
        questions = self._extract_open_questions(text)

        return MeetingNotesAnalysis(
            file_path=file_path,
            kpis=kpis,
            dimensions=dimensions,
            filters=filters,
            business_rules=rules,
            open_questions=questions,
        )

    # ------------------------------------------------------------------
    # Extraction methods
    # ------------------------------------------------------------------

    def _extract_dimension_mentions(self, text: str) -> List[ExtractedDimension]:
        """Extract dimensions from 'by {X}', 'per {X}', 'split by {X}'."""
        dims: List[ExtractedDimension] = []
        seen: set = set()

        for match in self._DIM_PATTERN.finditer(text):
            raw = match.group(1).strip().rstrip(",;.")
            canonical = self._match_dimension(raw)
            key = canonical or raw.lower()
            if key in seen:
                continue
            seen.add(key)
            dims.append(
                ExtractedDimension(
                    name=canonical or raw.upper().replace(" ", "_"),
                    business_name=raw,
                    extracted_from="notes",
                    confidence=0.6 if canonical else 0.4,
                )
            )

        # Also check drill-down patterns
        for match in self._DRILL_PATTERN.finditer(text):
            raw = match.group(1).strip().rstrip(",;.")
            canonical = self._match_dimension(raw)
            key = canonical or raw.lower()
            if key in seen:
                continue
            seen.add(key)
            dims.append(
                ExtractedDimension(
                    name=canonical or raw.upper().replace(" ", "_"),
                    business_name=raw,
                    extracted_from="notes",
                    confidence=0.5 if canonical else 0.3,
                )
            )

        return dims

    def _extract_filter_mentions(self, text: str) -> List[ExtractedFilter]:
        """Extract filters from 'excluding {X}', 'only {X}'."""
        filters: List[ExtractedFilter] = []
        seen: set = set()

        for match in self._FILTER_PATTERN.finditer(text):
            raw = match.group(1).strip().rstrip(",;.")
            if raw.lower() in seen:
                continue
            seen.add(raw.lower())
            filters.append(
                ExtractedFilter(
                    description=f"{match.group(0).strip()}",
                    sql_expression="",
                    applies_to=[],
                    source="notes",
                    confidence=0.5,
                )
            )

        return filters

    def _extract_kpi_mentions(self, text: str) -> List[ExtractedKPI]:
        """Extract KPIs from 'total {X}', '{X} per {Y}'."""
        kpis: List[ExtractedKPI] = []
        seen: set = set()

        # Standard aggregation KPIs
        for match in self._KPI_PATTERN.finditer(text):
            raw = match.group(1).strip().rstrip(",;.")
            canonical = self._match_measure(raw)
            key = canonical or raw.lower()
            if key in seen:
                continue
            seen.add(key)
            kpis.append(
                ExtractedKPI(
                    name=canonical or raw.upper().replace(" ", "_"),
                    business_name=raw,
                    formula=match.group(0).strip(),
                    source="notes",
                    source_type="notes",
                    confidence=0.6 if canonical else 0.4,
                )
            )

        # Ratio KPIs
        for match in self._KPI_RATIO_PATTERN.finditer(text):
            numerator = match.group(1).strip()
            denominator = match.group(2).strip()
            name = f"{numerator}_per_{denominator}".upper().replace(" ", "_")
            if name.lower() in seen:
                continue
            seen.add(name.lower())
            kpis.append(
                ExtractedKPI(
                    name=name,
                    business_name=f"{numerator} per {denominator}",
                    formula=f"{numerator} / {denominator}",
                    source="notes",
                    source_type="notes",
                    confidence=0.5,
                )
            )

        return kpis

    def _extract_business_rules(self, text: str) -> List[str]:
        """Extract business rule statements."""
        rules: List[str] = []
        for match in self._RULE_PATTERN.finditer(text):
            rule = match.group(1).strip()
            if rule and len(rule) > 5:
                rules.append(rule)
        return rules

    def _extract_open_questions(self, text: str) -> List[str]:
        """Extract open questions from meeting notes."""
        questions: List[str] = []
        for match in self._QUESTION_PATTERN.finditer(text):
            q = match.group(1).strip()
            if q and len(q) > 10:
                questions.append(q)
        return questions[:20]  # Cap at 20

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _match_dimension(raw: str) -> str:
        """Match raw text to a known dimension keyword."""
        lower = raw.lower().strip()
        for keyword, dim_name in DIMENSION_KEYWORD_MAP.items():
            if keyword in lower:
                return dim_name
        return ""

    @staticmethod
    def _match_measure(raw: str) -> str:
        """Match raw text to a known measure keyword."""
        lower = raw.lower().strip()
        for keyword, measure_name in MEASURE_KEYWORD_MAP.items():
            if keyword in lower:
                return measure_name
        return ""
