"""Report Suggester — suggests report structures from BLCE metadata.

Given a report type (or empty for "all feasible"), this module inspects
the artifact cache and skill store, collects available measures, dimensions,
and filters, then suggests report structures with SQL templates.

All logic is deterministic — no LLM calls.
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional

from ..contracts import ReportSuggestion


# -----------------------------------------------------------------------
# Report type templates
# -----------------------------------------------------------------------

REPORT_TEMPLATES: Dict[str, Dict[str, Any]] = {
    "summary": {
        "description": "High-level summary with key metrics",
        "min_measures": 1,
    },
    "trend": {
        "description": "Time-series trend analysis",
        "min_measures": 1,
        "requires_date": True,
    },
    "comparison": {
        "description": "Cross-dimensional comparison",
        "min_measures": 1,
        "min_dimensions": 2,
    },
    "detail": {
        "description": "Detailed drill-down report",
        "min_measures": 2,
    },
    "kpi_dashboard": {
        "description": "KPI scorecard with targets",
        "min_measures": 3,
    },
}


class ReportSuggester:
    """Suggest report structures based on available BLCE metadata."""

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def suggest(
        self,
        report_type: str = "",
        *,
        artifact_cache: Optional[Dict[str, Dict[str, Any]]] = None,
        skill_store: Optional[Dict[str, Dict[str, Any]]] = None,
    ) -> List[ReportSuggestion]:
        """Return a list of ``ReportSuggestion`` objects.

        If *report_type* is empty, returns suggestions for all feasible
        report types.  Otherwise, returns a single suggestion (or empty
        list if not feasible).
        """
        artifact_cache = artifact_cache or {}
        skill_store = skill_store or {}

        available = self._collect_available(artifact_cache, skill_store)

        if report_type:
            template = REPORT_TEMPLATES.get(report_type)
            if not template:
                return []
            suggestion = self._suggest_for_type(report_type, template, available)
            return [suggestion] if suggestion else []

        # Suggest all feasible types
        suggestions: List[ReportSuggestion] = []
        for rtype, template in REPORT_TEMPLATES.items():
            suggestion = self._suggest_for_type(rtype, template, available)
            if suggestion:
                suggestions.append(suggestion)
        return suggestions

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

    @staticmethod
    def _collect_available(
        artifact_cache: Dict[str, Dict[str, Any]],
        skill_store: Dict[str, Dict[str, Any]],
    ) -> Dict[str, Any]:
        """Collect all measures, dimensions, filters, and tables."""
        measures: List[str] = []
        dimensions: List[str] = []
        filters: List[str] = []
        tables: List[str] = []
        has_date_dim = False
        seen_measures: set = set()
        seen_dims: set = set()

        for _aid, ad in artifact_cache.items():
            # Measures
            for m in ad.get("objects", {}).get("measures", []):
                mname = m.get("name") or m.get("alias") or ""
                if mname and mname.lower() not in seen_measures:
                    measures.append(mname)
                    seen_measures.add(mname.lower())

            # Grain columns → dimensions
            for g in ad.get("objects", {}).get("grain_columns", []):
                gc = g.get("column") or g.get("alias") or ""
                if gc and gc.lower() not in seen_dims:
                    dimensions.append(gc)
                    seen_dims.add(gc.lower())
                    if any(d in gc.lower() for d in ("date", "month", "year", "quarter", "day", "period")):
                        has_date_dim = True

            # Filters
            for f in ad.get("objects", {}).get("filters", []):
                clause = f.get("source_clause") or f.get("column", "")
                if clause:
                    filters.append(clause)

            # Tables
            for dep in ad.get("objects", {}).get("dependencies", []):
                tname = dep.get("name", "")
                if tname and tname not in tables:
                    tables.append(tname)

        # Supplement from skills
        for _sid, sd in skill_store.items():
            for mname in sd.get("measures_covered", []):
                if mname.lower() not in seen_measures:
                    measures.append(mname)
                    seen_measures.add(mname.lower())
            for gc in sd.get("grain_columns", []):
                if gc.lower() not in seen_dims:
                    dimensions.append(gc)
                    seen_dims.add(gc.lower())
                    if any(d in gc.lower() for d in ("date", "month", "year", "quarter", "day", "period")):
                        has_date_dim = True

        return {
            "measures": measures,
            "dimensions": dimensions,
            "filters": filters,
            "tables": tables,
            "has_date_dim": has_date_dim,
        }

    def _suggest_for_type(
        self,
        report_type: str,
        template: Dict[str, Any],
        available: Dict[str, Any],
    ) -> Optional[ReportSuggestion]:
        """Create a suggestion for a single report type if feasible."""
        measures = available["measures"]
        dimensions = available["dimensions"]
        filters = available["filters"]
        tables = available["tables"]

        # Check feasibility
        min_measures = template.get("min_measures", 1)
        if len(measures) < min_measures:
            return None

        if template.get("requires_date") and not available["has_date_dim"]:
            return None

        min_dims = template.get("min_dimensions", 0)
        if min_dims and len(dimensions) < min_dims:
            return None

        # Pick measures and dimensions for suggestion
        use_measures = measures[:min(5, len(measures))]
        use_dims = dimensions[:min(4, len(dimensions))]
        use_filters = filters[:min(3, len(filters))]

        # Confidence: base 0.5, +0.1 per measure (max 5), +0.05 per dim (max 4)
        confidence = 0.50
        confidence += min(len(use_measures), 5) * 0.08
        confidence += min(len(use_dims), 4) * 0.025

        primary_table = tables[0] if tables else "source_table"
        sql_template = self._build_sql_template(use_measures, use_dims, use_filters, primary_table)

        title = f"{report_type.replace('_', ' ').title()} Report"

        return ReportSuggestion(
            report_type=report_type,
            title=title,
            description=template["description"],
            measures=use_measures,
            dimensions=use_dims,
            filters=use_filters,
            sql_template=sql_template,
            confidence=round(min(confidence, 1.0), 2),
        )

    @staticmethod
    def _build_sql_template(
        measures: List[str],
        dimensions: List[str],
        filters: List[str],
        table: str,
    ) -> str:
        """Assemble a SQL template for the report."""
        select_parts: List[str] = list(dimensions) + measures
        if not select_parts:
            select_parts = ["*"]

        sql = f"SELECT {', '.join(select_parts)}\nFROM {table}"

        if filters:
            sql += f"\nWHERE {' AND '.join(filters)}"

        if dimensions:
            sql += f"\nGROUP BY {', '.join(dimensions)}"
            sql += f"\nORDER BY {', '.join(dimensions)}"

        return sql
