"""Rich HTML Report Generator for DataBridge platform runs.

Produces styled HTML reports with KPI tiles, bus matrix, classification
breakdown, star schema diagram, quality results, and phase timeline.

CSS reused from the Enertia E2E test report for visual consistency.
"""

from __future__ import annotations

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


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


# ---------------------------------------------------------------------------
# CSS (extracted from Enertia E2E report for reuse)
# ---------------------------------------------------------------------------

_CSS = """\
:root {
  --bg: #f3f6f9; --card: #ffffff; --ink: #142433; --muted: #4f6475;
  --line: #d5e0ea; --accent: #0f766e; --accent2: #0b5fa5;
  --warn: #9a3412; --purple: #7c3aed; --rose: #be185d;
}
* { box-sizing: border-box; }
body {
  margin: 0; font-family: "Segoe UI", Tahoma, sans-serif;
  color: var(--ink); background: linear-gradient(135deg, #edf4ff 0%, #f7fafc 40%, #eefcf6 100%);
  line-height: 1.5;
}
.wrap { max-width: 1280px; margin: 0 auto; padding: 24px 20px 48px; }
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 22px 24px; margin-bottom: 18px;
  box-shadow: 0 8px 28px rgba(20,36,51,0.06);
}
h1 { font-size: 28px; margin: 0 0 6px; }
h2 { font-size: 21px; color: var(--accent2); margin: 0 0 10px; border-bottom: 2px solid var(--line); padding-bottom: 6px; }
h3 { font-size: 16px; color: var(--accent); margin: 14px 0 6px; }
p { margin: 6px 0; }
.meta { color: var(--muted); font-size: 13px; }
.kpi {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px; margin-top: 12px;
}
.tile {
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
  background: linear-gradient(135deg, #fbfdff, #f0f7ff); text-align: center;
}
.tile b { display: block; font-size: 26px; color: var(--accent2); }
.tile small { color: var(--muted); font-size: 12px; }
table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px; }
th, td { border: 1px solid var(--line); padding: 7px 9px; vertical-align: top; text-align: left; }
th { background: #eef5fb; font-size: 12px; text-transform: uppercase; letter-spacing: 0.3px; }
code { font-family: Consolas, "Courier New", monospace; font-size: 12px; background: #f1f5f9; padding: 1px 4px; border-radius: 3px; }
pre {
  font-family: Consolas, "Courier New", monospace; font-size: 12px;
  background: #0f172a; color: #e2e8f0; border-radius: 10px;
  padding: 14px; overflow-x: auto; line-height: 1.4;
}
.badge {
  display: inline-block; border-radius: 999px; padding: 2px 10px;
  font-size: 11px; font-weight: 600; margin-right: 5px; margin-bottom: 4px;
}
.badge-green { background: #def7ec; color: #065f46; border: 1px solid #a7f3d0; }
.badge-blue { background: #dbeafe; color: #1e40af; border: 1px solid #93c5fd; }
.badge-purple { background: #ede9fe; color: #5b21b6; border: 1px solid #c4b5fd; }
.badge-orange { background: #ffedd5; color: #9a3412; border: 1px solid #fdba74; }
.badge-rose { background: #ffe4e6; color: #9f1239; border: 1px solid #fda4af; }
.phase-tag {
  display: inline-block; background: var(--accent2); color: #fff;
  border-radius: 6px; padding: 2px 8px; font-size: 11px; font-weight: 700;
  margin-right: 6px; vertical-align: middle;
}
.bus-matrix th { font-size: 11px; writing-mode: vertical-rl; text-orientation: mixed; padding: 4px 3px; min-width: 32px; text-align: center; }
.bus-matrix td { text-align: center; padding: 5px 3px; font-size: 13px; }
.bus-matrix td.proc { text-align: left; font-weight: 600; font-size: 12px; }
.bus-matrix .check { background: #d1fae5; color: #065f46; font-weight: 700; }
.pass { color: #065f46; font-weight: 700; }
.fail { color: #9f1239; font-weight: 700; }
.timeline-bar {
  height: 18px; border-radius: 4px; background: var(--accent2); display: inline-block;
  margin-right: 4px; min-width: 2px;
}
.footnote { font-size: 11px; color: var(--muted); margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
"""


class ReportGenerator:
    """Build rich HTML reports for DataBridge platform runs."""

    def __init__(self, run_id: str, output_dir: Path | str = "data/artifacts"):
        self.run_id = run_id
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self._sections: List[str] = []

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

    def add_header(self, title: str = "DataBridge Platform Report", subtitle: str = "") -> None:
        """Add report header with title."""
        self._sections.append(f"""
<div class="card">
  <h1>{_esc(title)}</h1>
  <p class="meta">Run ID: <code>{_esc(self.run_id)}</code> &nbsp;|&nbsp;
    Generated: {datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")}</p>
  {f'<p class="meta">{_esc(subtitle)}</p>' if subtitle else ''}
</div>
""")

    def add_kpi_tiles(
        self,
        total_tables: int = 0,
        relationships: int = 0,
        dimensions: int = 0,
        facts: int = 0,
        coverage_pct: float = 0.0,
        duration_seconds: float = 0.0,
        **extra: Any,
    ) -> None:
        """Add KPI tile summary."""
        tiles = [
            ("Tables", total_tables),
            ("Relationships", relationships),
            ("Dimensions", dimensions),
            ("Facts", facts),
            ("Coverage", f"{coverage_pct:.0f}%"),
            ("Duration", f"{duration_seconds:.0f}s"),
        ]
        for label, val in extra.items():
            tiles.append((label.replace("_", " ").title(), val))

        tiles_html = "".join(
            f'<div class="tile"><b>{_esc(str(v))}</b><small>{_esc(k)}</small></div>'
            for k, v in tiles
        )
        self._sections.append(f"""
<div class="card">
  <h2>Summary</h2>
  <div class="kpi">{tiles_html}</div>
</div>
""")

    def add_bus_matrix(self, matrix_result: Dict[str, Any]) -> None:
        """Add Kimball bus matrix section."""
        try:
            from src.data_modeling.bus_matrix import BusMatrixGenerator
            gen = BusMatrixGenerator()
            matrix_html = gen.to_html(matrix_result)
        except Exception:
            matrix_html = "<p>Bus matrix not available.</p>"

        self._sections.append(f"""
<div class="card">
  <h2>Kimball Bus Matrix</h2>
  {matrix_html}
</div>
""")

    def add_classification_summary(self, classification: Dict[str, str]) -> None:
        """Add dimension/fact/bridge/unknown breakdown."""
        counts: Dict[str, int] = {}
        for cls in classification.values():
            counts[cls] = counts.get(cls, 0) + 1

        badge_map = {
            "dimension": "badge-blue",
            "fact": "badge-purple",
            "bridge": "badge-orange",
            "unknown": "badge-rose",
        }
        badges = "".join(
            f'<span class="badge {badge_map.get(cls, "badge-rose")}">{_esc(cls)}: {cnt}</span>'
            for cls, cnt in sorted(counts.items())
        )

        rows = "".join(
            f"<tr><td>{_esc(table)}</td><td>{_esc(cls)}</td></tr>"
            for table, cls in sorted(classification.items())
        )

        self._sections.append(f"""
<div class="card">
  <h2>Table Classification</h2>
  <p>{badges}</p>
  <table>
    <tr><th>Table</th><th>Classification</th></tr>
    {rows}
  </table>
</div>
""")

    def add_star_schema(
        self,
        facts: List[str],
        dimensions: List[str],
        relationships: Optional[List[Dict[str, Any]]] = None,
    ) -> None:
        """Add CSS grid star schema diagram."""
        dim_items = "".join(
            f'<div class="tile" style="font-size:12px"><b style="font-size:14px">{_esc(d[:25])}</b><small>Dimension</small></div>'
            for d in dimensions[:12]
        )
        fact_items = "".join(
            f'<div class="tile" style="background:linear-gradient(135deg,#f0f7ff,#e8f4ff)"><b style="font-size:14px;color:var(--purple)">{_esc(f[:25])}</b><small>Fact</small></div>'
            for f in facts
        )

        self._sections.append(f"""
<div class="card">
  <h2>Star Schema Overview</h2>
  <h3>Fact Tables</h3>
  <div class="kpi">{fact_items}</div>
  <h3>Dimension Tables</h3>
  <div class="kpi">{dim_items}</div>
</div>
""")

    def add_quality_results(self, suite_results: List[Dict[str, Any]]) -> None:
        """Add quality expectation results (flat expectation-level items).

        Each item should have: column, expectation/type, passed/status, severity.
        If items look like suite summaries (have 'suite_name' or 'expectations' keys),
        delegates to add_quality_suites() instead.
        """
        if not suite_results:
            return

        # Auto-detect suite summaries vs flat expectations
        first = suite_results[0]
        if "suite_name" in first or ("expectations" in first and "column" not in first):
            self.add_quality_suites(suite_results)
            return

        rows = ""
        pass_count = fail_count = 0
        for r in suite_results:
            passed = r.get("passed", r.get("status") == "pass")
            if passed:
                pass_count += 1
                status_html = '<span class="pass">PASS</span>'
            else:
                fail_count += 1
                status_html = '<span class="fail">FAIL</span>'
            rows += (
                f"<tr><td>{_esc(r.get('column', ''))}</td>"
                f"<td>{_esc(r.get('expectation', r.get('type', '')))}</td>"
                f"<td>{status_html}</td>"
                f"<td>{_esc(r.get('severity', ''))}</td></tr>\n"
            )

        self._sections.append(f"""
<div class="card">
  <h2>Data Quality Results</h2>
  <p><span class="badge badge-green">Pass: {pass_count}</span>
     <span class="badge badge-rose">Fail: {fail_count}</span></p>
  <table>
    <tr><th>Column</th><th>Expectation</th><th>Result</th><th>Severity</th></tr>
    {rows}
  </table>
</div>
""")

    def add_quality_suites(self, suites: List[Dict[str, Any]]) -> None:
        """Add quality suite summaries (suite-level, not individual expectations).

        Each item should have: table, suite_name, expectations (count).
        """
        if not suites:
            return

        total_expectations = sum(s.get("expectations", 0) for s in suites)
        rows = ""
        for s in suites:
            table = s.get("table", s.get("table_name", ""))
            suite_name = s.get("suite_name", "")
            exp_count = s.get("expectations", 0)
            status = s.get("status", "generated")
            colour = "#065f46" if exp_count > 0 else "#9a3412"
            rows += (
                f"<tr><td>{_esc(table)}</td>"
                f"<td><code>{_esc(suite_name)}</code></td>"
                f"<td style='text-align:center'><b>{exp_count}</b></td>"
                f"<td style='color:{colour}'>{_esc(status)}</td></tr>\n"
            )

        self._sections.append(f"""
<div class="card">
  <h2>Data Quality Suites</h2>
  <p><span class="badge badge-blue">Suites: {len(suites)}</span>
     <span class="badge badge-purple">Expectations: {total_expectations}</span></p>
  <table>
    <tr><th>Table</th><th>Suite</th><th>Expectations</th><th>Status</th></tr>
    {rows}
  </table>
</div>
""")

    def add_phase_timeline(self, phases: List[Dict[str, Any]]) -> None:
        """Add horizontal phase duration timeline."""
        max_dur = max((p.get("duration_seconds", 0) for p in phases), default=1) or 1

        rows = ""
        for p in phases:
            name = p.get("name", "")
            status = p.get("status", "unknown")
            dur = p.get("duration_seconds", 0)
            pct = max(2, int((dur / max_dur) * 200))
            colour = {"completed": "#0f766e", "skipped": "#6c757d", "error": "#dc3545"}.get(status, "#ffc107")
            rows += (
                f"<tr><td><span class='phase-tag'>{_esc(name)}</span></td>"
                f"<td style='color:{colour};font-weight:bold'>{_esc(status)}</td>"
                f"<td>{dur:.1f}s</td>"
                f"<td><span class='timeline-bar' style='width:{pct}px;background:{colour}'></span></td>"
                f"<td>{_esc(p.get('error', '') or '')}</td></tr>\n"
            )

        self._sections.append(f"""
<div class="card">
  <h2>Phase Timeline</h2>
  <table>
    <tr><th>Phase</th><th>Status</th><th>Duration</th><th>Timeline</th><th>Error</th></tr>
    {rows}
  </table>
</div>
""")

    def add_custom_section(self, title: str, content_html: str) -> None:
        """Add an arbitrary HTML section wrapped in a card."""
        self._sections.append(f"""
<div class="card">
  <h2>{_esc(title)}</h2>
  {content_html}
</div>
""")

    def add_log_console(self, log_lines: List[str], title: str = "Pipeline Log") -> None:
        """Add a collapsible log/audit console showing timestamped log entries.

        Args:
            log_lines: List of log message strings (one per line).
            title: Section title.
        """
        escaped = "\n".join(_esc(line) for line in log_lines[-2000:])  # Cap at 2000 lines
        total = len(log_lines)
        shown = min(total, 2000)
        self._sections.append(f"""
<div class="card">
  <h2>{_esc(title)}</h2>
  <p>{total} log entries ({shown} shown).
     <button onclick="var el=this.parentElement.nextElementSibling; el.style.display=el.style.display==='none'?'block':'none';"
             style="cursor:pointer; padding:4px 12px; border:1px solid #6366f1; border-radius:4px; background:#eef2ff; color:#4338ca;">
       Toggle Log
     </button>
  </p>
  <pre style="display:none; max-height:600px; overflow-y:auto; background:#1e1e2e; color:#cdd6f4; padding:12px; border-radius:8px; font-size:12px; line-height:1.5; white-space:pre-wrap;">{escaped}</pre>
</div>
""")

    # ------------------------------------------------------------------
    # Generate
    # ------------------------------------------------------------------

    def generate(self) -> Path:
        """Render all sections into a single HTML file.

        Returns:
            Path to the generated HTML report.
        """
        body = "\n".join(self._sections)
        footer = (
            f'<div class="footnote">Generated by DataBridge AI v0.42 '
            f"at {datetime.utcnow().isoformat()}Z</div>"
        )

        full_html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>DataBridge Report — {_esc(self.run_id)}</title>
<style>
{_CSS}
</style>
</head>
<body>
<div class="wrap">
{body}
{footer}
</div>
</body>
</html>"""

        path = self.output_dir / f"{self.run_id}_report.html"
        path.write_text(full_html, encoding="utf-8")
        return path

    # ------------------------------------------------------------------
    # Factory
    # ------------------------------------------------------------------

    @classmethod
    def from_run_result(
        cls,
        run_result: Dict[str, Any],
        output_dir: Path | str = "data/artifacts",
    ) -> Path:
        """Generate a rich report from a standardised RunResult dict.

        Expected keys in run_result:
            run_id, status, phases, total_duration_seconds,
            tables (list), relationships (list), classification (dict),
            bus_matrix (optional dict), quality_results (optional list).

        Returns:
            Path to the generated HTML report.
        """
        run_id = run_result.get("run_id", "unknown")
        rg = cls(run_id=run_id, output_dir=output_dir)

        # Header
        rg.add_header(
            title="DataBridge Platform Report",
            subtitle=f"Status: {run_result.get('status', 'unknown')}",
        )

        # KPIs
        classification = run_result.get("classification", {})
        dims = [t for t, c in classification.items() if c == "dimension"]
        facts = [t for t, c in classification.items() if c == "fact"]

        rg.add_kpi_tiles(
            total_tables=len(run_result.get("tables", [])),
            relationships=len(run_result.get("relationships", [])),
            dimensions=len(dims),
            facts=len(facts),
            coverage_pct=run_result.get("coverage_pct", 0),
            duration_seconds=run_result.get("total_duration_seconds", 0),
        )

        # Phase timeline
        phases = run_result.get("phases", [])
        if phases:
            rg.add_phase_timeline(phases)

        # Classification
        if classification:
            rg.add_classification_summary(classification)

        # Bus matrix
        bm = run_result.get("bus_matrix")
        if bm:
            rg.add_bus_matrix(bm)

        # Star schema
        if dims or facts:
            rg.add_star_schema(facts, dims, run_result.get("relationships"))

        # Quality
        qr = run_result.get("quality_results", [])
        if qr:
            rg.add_quality_results(qr)

        return rg.generate()
