"""Bridge BLCE ReportAnalysis output to Report Parity ReportSpec objects."""
from __future__ import annotations

import logging
import re
from typing import Any, Dict, List, Optional

from .contracts import ReportLineItem, ReportSpec
from .report_spec_compiler import ReportSpecCompiler

logger = logging.getLogger(__name__)

# Temporal keyword patterns for dimension detection
_TEMPORAL_KEYWORDS = ("DATE", "MONTH", "YEAR", "QUARTER")


class BLCEParityBridge:
    """Bridge BLCE ReportAnalysis output to Report Parity ReportSpec objects.

    Converts typed BLCE Pydantic models (``ReportAnalysis``) — or their dict
    serializations — into ``ReportSpec`` objects that the Report Parity
    progressive validation engine can consume.

    Follows the established bridge pattern (cf. ``fix_loop_bridge.py``,
    ``shield_bridge.py``).
    """

    def __init__(self) -> None:
        self._compiler = ReportSpecCompiler()

    # ------------------------------------------------------------------
    # Single report conversion
    # ------------------------------------------------------------------

    def convert_report_analysis(
        self,
        report_analysis: Any,
        expected_values: Optional[Dict[str, Dict[str, float]]] = None,
    ) -> ReportSpec:
        """Convert a BLCE ``ReportAnalysis`` to a ``ReportSpec``.

        Args:
            report_analysis: A BLCE ``ReportAnalysis`` Pydantic model **or** a
                plain dict with the same shape.
            expected_values: Optional mapping of KPI name → {period: value}.
                When provided, these are attached to the corresponding
                ``ReportLineItem.expected_values``.

        Returns:
            A compiled ``ReportSpec`` ready for progressive validation.
        """
        expected_values = expected_values or {}

        # Normalise to dict if a Pydantic model was passed
        data = self._to_dict(report_analysis)

        # Extract components
        kpis = data.get("kpis", [])
        dimensions = data.get("dimensions", [])
        filters = data.get("filters", [])

        # Build line items from KPIs
        line_items: List[Dict[str, Any]] = []
        for kpi in kpis:
            kpi_dict = self._to_dict(kpi)
            name = kpi_dict.get("name", "")
            formula = kpi_dict.get("formula", "")
            source_measures = self._compiler._extract_measures(formula) if formula else []
            departments = kpi_dict.get("departments", [])
            confidence = kpi_dict.get("confidence", 0.0)
            expected = expected_values.get(name, {})

            line_items.append({
                "label": name,
                "expression": formula,
                "source_measures": source_measures,
                "filters": [],
                "expected": expected,
                "group": kpi_dict.get("source_type", ""),
            })

        # Extract grain columns and detect temporal column
        grain_columns: List[str] = []
        temporal_column = ""
        temporal_grain = "monthly"

        for dim in dimensions:
            dim_dict = self._to_dict(dim)
            dim_name = dim_dict.get("name", "")
            grain_columns.append(dim_name)

            if not temporal_column:
                upper_name = dim_name.upper()
                if any(kw in upper_name for kw in _TEMPORAL_KEYWORDS):
                    temporal_column = dim_name
                    if "MONTH" in upper_name:
                        temporal_grain = "monthly"
                    elif "QUARTER" in upper_name:
                        temporal_grain = "quarterly"
                    elif "YEAR" in upper_name:
                        temporal_grain = "yearly"

        # Build global filters
        global_filters: List[Dict[str, str]] = []
        for flt in filters:
            flt_dict = self._to_dict(flt)
            applies_to = flt_dict.get("applies_to", [])
            column = applies_to[0] if applies_to else ""
            global_filters.append({
                "column": column,
                "expression": flt_dict.get("sql_expression", ""),
            })

        # Build metadata from KPI departments/confidence
        metadata: Dict[str, Any] = {}
        all_departments: List[str] = []
        for kpi in kpis:
            kpi_dict = self._to_dict(kpi)
            all_departments.extend(kpi_dict.get("departments", []))
        if all_departments:
            metadata["departments"] = sorted(set(all_departments))

        # Derive report name from file_path or analysis_id
        report_name = data.get("file_path", "") or data.get("analysis_id", "BLCE Report")

        return self._compiler.compile(
            report_name=report_name,
            source_system="blce",
            line_items=line_items,
            grain_columns=grain_columns,
            temporal_column=temporal_column,
            temporal_grain=temporal_grain,
            global_filters=global_filters,
            metadata=metadata,
        )

    # ------------------------------------------------------------------
    # Batch conversion
    # ------------------------------------------------------------------

    def convert_batch(
        self,
        report_analyses: List[Any],
        expected_values_map: Optional[Dict[str, Dict[str, Dict[str, float]]]] = None,
    ) -> List[ReportSpec]:
        """Convert multiple ``ReportAnalysis`` objects to ``ReportSpec`` list.

        Args:
            report_analyses: List of ``ReportAnalysis`` models or dicts.
            expected_values_map: Optional mapping keyed by report index (str)
                or ``analysis_id`` → {kpi_name: {period: value}}.

        Returns:
            List of compiled ``ReportSpec`` objects.
        """
        expected_values_map = expected_values_map or {}
        specs: List[ReportSpec] = []

        for idx, analysis in enumerate(report_analyses):
            data = self._to_dict(analysis)
            analysis_id = data.get("analysis_id", "")
            # Look up expected values by index (str) or by analysis_id
            ev = expected_values_map.get(str(idx)) or expected_values_map.get(analysis_id, {})
            specs.append(self.convert_report_analysis(analysis, expected_values=ev))

        return specs

    # ------------------------------------------------------------------
    # Context extraction (for E2E orchestrator integration)
    # ------------------------------------------------------------------

    def extract_from_blce_context(
        self,
        blce_context: Dict[str, Any],
    ) -> List[ReportSpec]:
        """Extract ``ReportSpec`` objects from BLCE pipeline context.

        Reads ``blce_context["report_analyses"]`` (list of serialized
        ``ReportAnalysis`` dicts set by BLCE phase 6) and optionally
        ``blce_context["notes_analyses"]`` for KPIs from meeting notes.

        Args:
            blce_context: The orchestrator context dict.

        Returns:
            List of ``ReportSpec`` objects (empty list if no reports found).
        """
        report_analyses = blce_context.get("report_analyses", [])
        notes_analyses = blce_context.get("notes_analyses", [])

        combined = list(report_analyses) + list(notes_analyses)
        if not combined:
            return []

        return self.convert_batch(combined)

    # ------------------------------------------------------------------
    # Internal helpers
    # ------------------------------------------------------------------

    @staticmethod
    def _to_dict(obj: Any) -> Dict[str, Any]:
        """Convert a Pydantic model or dict to a plain dict."""
        if isinstance(obj, dict):
            return obj
        if hasattr(obj, "model_dump"):
            return obj.model_dump()
        if hasattr(obj, "dict"):
            return obj.dict()
        return dict(obj)
