"""BLCE Proposal Generator — branded HTML proposals from ProposedModel.

Generates client-facing HTML documents showing the proposed Kimball model
for sign-off: executive summary, dimension/fact tables, bus matrix,
quality rules, measure validations, and per-object rationale.
"""
from __future__ import annotations

import html
import logging
from pathlib import Path
from typing import Any, Dict, List, Optional

from .contracts import (
    MeasureValidation,
    ProposedDimension,
    ProposedFact,
    ProposedModel,
)

logger = logging.getLogger(__name__)


def _esc(text: Any) -> str:
    """HTML-escape text safely."""
    return html.escape(str(text))


def _confidence_badge(score: float) -> str:
    """Return a colour-coded badge for a confidence score."""
    if score >= 0.8:
        cls = "badge-green"
    elif score >= 0.5:
        cls = "badge-blue"
    elif score >= 0.3:
        cls = "badge-orange"
    else:
        cls = "badge-rose"
    return f'<span class="badge {cls}">{score:.0%}</span>'


def _scd_badge(scd_type: int) -> str:
    """Return a badge for SCD type."""
    labels = {0: ("SCD0", "badge-purple"), 1: ("SCD1", "badge-blue"), 2: ("SCD2", "badge-green")}
    label, cls = labels.get(scd_type, (f"SCD{scd_type}", "badge-orange"))
    return f'<span class="badge {cls}">{label}</span>'


class ProposalGenerator:
    """Build branded HTML proposals from a ProposedModel."""

    def generate_proposal(
        self,
        model: ProposedModel,
        client_name: str = "",
        handoff: Optional[Dict[str, Any]] = None,
        run_id: str = "",
        output_dir: str = "data/artifacts",
    ) -> Path:
        """Generate a full HTML proposal document.

        Args:
            model: The proposed Kimball model.
            client_name: Client name for branding.
            handoff: Optional E2E handoff dict for timeline data.
            run_id: Run identifier for filename.
            output_dir: Directory to write the HTML file.

        Returns:
            Path to the generated HTML file.
        """
        from src.artifacts.report_generator import ReportGenerator

        rid = run_id or model.model_id
        rg = ReportGenerator(run_id=rid, output_dir=output_dir)

        subtitle = f"Prepared for {client_name}" if client_name else ""
        rg.add_header(
            title="DataBridge AI \u2014 Data Warehouse Proposal",
            subtitle=subtitle,
        )

        # Executive summary as KPI tiles
        self._add_executive_summary(rg, model)

        # Proposed dimensions table
        rg.add_custom_section(
            "Proposed Dimensions",
            self._proposed_dimensions_table(model),
        )

        # Proposed facts table
        rg.add_custom_section(
            "Proposed Fact Tables",
            self._proposed_facts_table(model),
        )

        # Bus matrix
        if model.bus_matrix:
            self._add_bus_matrix(rg, model)

        # Quality rules
        if model.measure_validations:
            rg.add_custom_section(
                "Measure Validations",
                self._measure_validation_section(model),
            )

        # Business rules
        if model.business_rules:
            rg.add_custom_section(
                "Business Rules",
                self._business_rules_section(model),
            )

        # Per-object rationale
        rg.add_custom_section(
            "Design Rationale",
            self._rationale_section(model),
        )

        path = rg.generate()
        logger.info("Proposal generated: %s", path)
        return path

    # ------------------------------------------------------------------
    # Section builders
    # ------------------------------------------------------------------

    def _add_executive_summary(
        self, rg: Any, model: ProposedModel
    ) -> None:
        """Add KPI tiles summarising the proposal."""
        scd_counts = {"SCD0": 0, "SCD1": 0, "SCD2": 0}
        for d in model.dimensions:
            key = f"SCD{d.scd_type}"
            scd_counts[key] = scd_counts.get(key, 0) + 1

        avg_conf = 0.0
        items = list(model.dimensions) + list(model.facts)
        if items:
            avg_conf = sum(i.confidence for i in items) / len(items)

        mv_pass = sum(1 for v in model.measure_validations if v.severity == "info")
        mv_total = len(model.measure_validations)

        rg.add_kpi_tiles(
            dimensions=len(model.dimensions),
            facts=len(model.facts),
            relationships=sum(len(f.dimension_fks) for f in model.facts),
            total_tables=len(model.dimensions) + len(model.facts),
            coverage_pct=avg_conf * 100,
            SCD_Breakdown=f"{scd_counts['SCD0']}/{scd_counts['SCD1']}/{scd_counts['SCD2']}",
            Measure_Validations=f"{mv_pass}/{mv_total}" if mv_total else "N/A",
        )

    def _proposed_dimensions_table(self, model: ProposedModel) -> str:
        """Build HTML table for proposed dimensions."""
        if not model.dimensions:
            return "<p>No dimensions proposed.</p>"

        rows = ""
        for d in model.dimensions:
            rows += (
                f"<tr>"
                f"<td><b>{_esc(d.name)}</b></td>"
                f"<td>{_esc(d.business_name)}</td>"
                f"<td>{_esc(', '.join(d.source_tables[:3]))}</td>"
                f"<td><code>{_esc(d.natural_key)}</code></td>"
                f"<td>{_scd_badge(d.scd_type)}</td>"
                f"<td>{_esc(', '.join(d.hierarchy_levels[:4]))}</td>"
                f"<td>{_confidence_badge(d.confidence)}</td>"
                f"</tr>\n"
            )

        return (
            "<table>"
            "<tr><th>Name</th><th>Business Name</th><th>Source Tables</th>"
            "<th>Natural Key</th><th>SCD Type</th><th>Hierarchy Levels</th>"
            "<th>Confidence</th></tr>\n"
            f"{rows}</table>"
        )

    def _proposed_facts_table(self, model: ProposedModel) -> str:
        """Build HTML table for proposed facts."""
        if not model.facts:
            return "<p>No fact tables proposed.</p>"

        rows = ""
        for f in model.facts:
            union_badge = '<span class="badge badge-orange">UNION</span>' if f.needs_union else ""
            rows += (
                f"<tr>"
                f"<td><b>{_esc(f.name)}</b></td>"
                f"<td>{_esc(f.business_name)}</td>"
                f"<td><code>{_esc(f.grain)}</code></td>"
                f"<td>{_esc(', '.join(f.measures[:5]))}</td>"
                f"<td>{_esc(', '.join(f.dimension_fks[:5]))}</td>"
                f"<td>{_esc(', '.join(f.source_tables[:3]))}</td>"
                f"<td>{union_badge}</td>"
                f"<td>{_confidence_badge(f.confidence)}</td>"
                f"</tr>\n"
            )

        return (
            "<table>"
            "<tr><th>Name</th><th>Business Name</th><th>Grain</th>"
            "<th>Measures</th><th>Dimension FKs</th><th>Source Tables</th>"
            "<th>Union?</th><th>Confidence</th></tr>\n"
            f"{rows}</table>"
        )

    def _add_bus_matrix(self, rg: Any, model: ProposedModel) -> None:
        """Add bus matrix section using existing BusMatrixGenerator."""
        try:
            from src.data_modeling.bus_matrix import BusMatrixGenerator
            gen = BusMatrixGenerator()
            matrix_html = gen.to_html(model.bus_matrix)
            rg.add_custom_section("Kimball Bus Matrix", matrix_html)
        except Exception:
            rg.add_custom_section("Kimball Bus Matrix", "<p>Bus matrix not available.</p>")

    def _measure_validation_section(self, model: ProposedModel) -> str:
        """Show P2.2 measure validations with severity badges."""
        if not model.measure_validations:
            return "<p>No measure validations available.</p>"

        severity_badge = {
            "info": "badge-green",
            "warning": "badge-orange",
            "error": "badge-rose",
        }

        rows = ""
        for v in model.measure_validations:
            cls = severity_badge.get(v.severity, "badge-blue")
            rows += (
                f"<tr>"
                f"<td>{_esc(v.fact_name)}</td>"
                f"<td><code>{_esc(v.measure_name)}</code></td>"
                f'<td><span class="badge {cls}">{_esc(v.severity)}</span></td>'
                f"<td>{'Yes' if v.exists_in_source else 'No'}</td>"
                f"<td>{'Yes' if v.is_numeric else 'No'}</td>"
                f"<td>{_esc(v.warning or '-')}</td>"
                f"</tr>\n"
            )

        return (
            "<table>"
            "<tr><th>Fact</th><th>Measure</th><th>Severity</th>"
            "<th>Exists</th><th>Numeric</th><th>Warning</th></tr>\n"
            f"{rows}</table>"
        )

    def _business_rules_section(self, model: ProposedModel) -> str:
        """List business rules as an ordered list."""
        if not model.business_rules:
            return "<p>No business rules captured.</p>"

        items = "".join(f"<li>{_esc(r)}</li>" for r in model.business_rules)
        return f"<ol>{items}</ol>"

    def _rationale_section(self, model: ProposedModel) -> str:
        """Per-dimension and per-fact rationale with confidence."""
        parts: List[str] = []

        if model.dimensions:
            parts.append("<h3>Dimensions</h3>")
            for d in model.dimensions:
                parts.append(
                    f"<p><b>{_esc(d.name)}</b> {_confidence_badge(d.confidence)}<br>"
                    f"{_esc(d.rationale or 'No rationale provided.')}</p>"
                )

        if model.facts:
            parts.append("<h3>Fact Tables</h3>")
            for f in model.facts:
                parts.append(
                    f"<p><b>{_esc(f.name)}</b> {_confidence_badge(f.confidence)}<br>"
                    f"{_esc(f.rationale or 'No rationale provided.')}</p>"
                )

        return "\n".join(parts) if parts else "<p>No rationale available.</p>"
