"""Bus Matrix Generator — Kimball-style fact×dimension conformance grid.

Generates a bus matrix from relationship and classification data, with
Markdown and HTML output formats.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional, Set


def _html_escape(text: str) -> str:
    return str(text).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")


class BusMatrixGenerator:
    """Generate a Kimball bus matrix from relationships and table classifications."""

    def generate(
        self,
        relationships: List[Dict[str, Any]],
        classification: Dict[str, str],
        threshold: float = 0.0,
    ) -> Dict[str, Any]:
        """Build a bus matrix from relationships and classification.

        Args:
            relationships: List of relationship dicts with source_table/target_table.
            classification: Dict mapping table_name → 'dimension'|'fact'|'bridge'|'unknown'.
            threshold: Minimum confidence/overlap to include a relationship (default: include all).

        Returns:
            Dict with dimensions, facts, matrix, conformance_score, conformed_dimensions.
        """
        dims = sorted(t for t, c in classification.items() if c == "dimension")
        facts = sorted(t for t, c in classification.items() if c == "fact")

        if not dims or not facts:
            return {
                "dimensions": dims,
                "facts": facts,
                "matrix": {},
                "conformance_score": 0.0,
                "conformed_dimensions": [],
            }

        # Build matrix: fact -> {dim: True/False}
        matrix: Dict[str, Dict[str, bool]] = {f: {d: False for d in dims} for f in facts}

        for rel in relationships:
            overlap = rel.get("overlap", 1.0) or 1.0
            if overlap < threshold:
                continue

            src = rel.get("source_table", "")
            tgt = rel.get("target_table", "")

            # fact → dim relationship
            if src in matrix and tgt in dims:
                matrix[src][tgt] = True
            elif tgt in matrix and src in dims:
                matrix[tgt][src] = True

        # Conformance: dimensions used by 2+ facts
        dim_usage: Dict[str, int] = {d: 0 for d in dims}
        for fact_dims in matrix.values():
            for d, used in fact_dims.items():
                if used:
                    dim_usage[d] += 1

        conformed = sorted(d for d, count in dim_usage.items() if count >= 2)

        # Score: % of cells that are True
        total_cells = len(facts) * len(dims)
        filled = sum(1 for f in matrix for d in matrix[f] if matrix[f][d])
        score = round(filled / total_cells, 3) if total_cells > 0 else 0.0

        return {
            "dimensions": dims,
            "facts": facts,
            "matrix": matrix,
            "conformance_score": score,
            "conformed_dimensions": conformed,
        }

    def to_markdown(self, result: Dict[str, Any]) -> str:
        """Render bus matrix as a Markdown table."""
        dims = result.get("dimensions", [])
        facts = result.get("facts", [])
        matrix = result.get("matrix", {})

        if not dims or not facts:
            return "_No bus matrix available (need both dimensions and facts)._"

        # Header
        header = "| Fact \\\\ Dimension | " + " | ".join(d[:20] for d in dims) + " |"
        sep = "|" + "---|" * (len(dims) + 1)

        rows = []
        for fact in facts:
            cells = []
            for dim in dims:
                cells.append("X" if matrix.get(fact, {}).get(dim, False) else " ")
            rows.append(f"| {fact} | " + " | ".join(cells) + " |")

        parts = [header, sep] + rows
        parts.append(f"\n_Conformance: {result.get('conformance_score', 0):.1%} "
                      f"| Conformed dims: {len(result.get('conformed_dimensions', []))} of {len(dims)}_")
        return "\n".join(parts)

    def to_html(self, result: Dict[str, Any]) -> str:
        """Render bus matrix as styled HTML table."""
        dims = result.get("dimensions", [])
        facts = result.get("facts", [])
        matrix = result.get("matrix", {})

        if not dims or not facts:
            return "<p>No bus matrix available.</p>"

        html_parts = [
            '<table class="bus-matrix">',
            "<thead><tr><th>Fact \\ Dimension</th>",
        ]

        for d in dims:
            short = _html_escape(d[:20])
            html_parts.append(f'<th class="rotate"><div><span>{short}</span></div></th>')
        html_parts.append("</tr></thead><tbody>")

        for fact in facts:
            html_parts.append(f'<tr><td class="proc">{_html_escape(fact)}</td>')
            for dim in dims:
                if matrix.get(fact, {}).get(dim, False):
                    html_parts.append('<td class="check">&#10003;</td>')
                else:
                    html_parts.append("<td></td>")
            html_parts.append("</tr>")

        html_parts.append("</tbody></table>")
        html_parts.append(
            f'<p class="conformance">Conformance: {result.get("conformance_score", 0):.1%} '
            f'| Conformed: {len(result.get("conformed_dimensions", []))} of {len(dims)} dimensions</p>'
        )

        return "\n".join(html_parts)

    @staticmethod
    def bus_matrix_css() -> str:
        """Return CSS for styling the bus matrix HTML."""
        return """
.bus-matrix { border-collapse: collapse; font-size: 0.85em; }
.bus-matrix th, .bus-matrix td { border: 1px solid #ddd; padding: 6px 10px; text-align: center; }
.bus-matrix th { background: #f4f4f4; }
.bus-matrix .rotate { height: 120px; white-space: nowrap; }
.bus-matrix .rotate div { transform: rotate(-60deg); width: 30px; }
.bus-matrix .proc { text-align: left; font-weight: bold; background: #f9f9f9; }
.bus-matrix .check { color: #28a745; font-weight: bold; font-size: 1.2em; }
.conformance { font-style: italic; color: #666; margin-top: 0.5em; }
"""
