"""Monocart dashboard generator (WPA-style monocart-report/index.html).

Fetches build history, enriches each build by decompressing the monocart
window.reportData (base64 + zlib deflate-raw) using Python's stdlib zlib —
no Node.js runtime required. Renders and saves the HTML dashboard.
"""
import base64
import json
import os
import sys
import zlib
from concurrent.futures import ThreadPoolExecutor, as_completed
from importlib.resources import files
from pathlib import Path

import requests

from .monocart_ops import batch_history


DEFAULT_DAYS = 30


# ---------------------------------------------------------------------------
# Monocart summary fetching  (pure Python, no Node.js)
# ---------------------------------------------------------------------------

def _walk_rows(rows: list, failed: list, flaky: list) -> None:
    """Recursively collect failed/flaky test titles from monocart row tree."""
    for row in rows or []:
        if row.get("type") == "case":
            status = row.get("caseType") or row.get("status", "")
            title = row.get("title") or row.get("name") or row.get("id") or ""
            if status == "failed":
                failed.append(title)
            elif status == "flaky":
                flaky.append(title)
        if row.get("subs"):
            _walk_rows(row["subs"], failed, flaky)


def fetch_monocart_summary(report_url: str) -> dict | None:
    """Fetch monocart index.html, decompress window.reportData, return summary.

    Replaces the Node.js fetch_monocart_summary.js helper entirely.
    """
    try:
        resp = requests.get(report_url, timeout=15, allow_redirects=True)
        if resp.status_code != 200:
            return None
        html = resp.text
    except requests.RequestException:
        return None

    import re
    m = re.search(r"window\.reportData\s*=\s*'([^']+)'", html)
    if not m:
        return None

    try:
        compressed = base64.b64decode(m.group(1))
        raw_json = zlib.decompress(compressed, -zlib.MAX_WBITS).decode("utf-8")
        report = json.loads(raw_json)
    except Exception:  # noqa: BLE001
        return None

    s = report.get("summary", {})

    def _get(key: str) -> int:
        val = s.get(key, 0)
        if isinstance(val, dict):
            return val.get("value", 0)
        return val or 0

    failed: list[str] = []
    flaky: list[str] = []
    _walk_rows(report.get("rows", []), failed, flaky)

    return {
        "passedTests":      _get("passed"),
        "failedTests":      _get("failed"),
        "flakyTests":       _get("flaky"),
        "skippedTests":     _get("skipped"),
        "failedTestTitles": failed,
        "flakyTestTitles":  flaky,
    }


def enrich_builds(builds: list[dict]) -> list[dict]:
    """Fetch monocart summaries for all builds in parallel."""

    def _fetch(build: dict) -> tuple[dict, dict | None]:
        return build, fetch_monocart_summary(build["reportUrl"])

    with ThreadPoolExecutor(max_workers=12) as pool:
        futures = [pool.submit(_fetch, b) for b in builds if b.get("reportUrl")]
        for future in as_completed(futures):
            build, summary = future.result()
            if summary:
                build["testsPassed"]   = summary.get("passedTests",      0)
                build["testsFailed"]   = summary.get("failedTests",       0)
                build["testsFlaky"]    = summary.get("flakyTests",        0)
                build["testsSkipped"]  = summary.get("skippedTests",      0)
                build["_failedTitles"] = summary.get("failedTestTitles",  [])
                build["_flakyTitles"]  = summary.get("flakyTestTitles",   [])
            else:
                build["testsPassed"]  = None
                build["testsFailed"]  = None
                build["testsFlaky"]   = None
                build["testsSkipped"] = None

    return builds


# ---------------------------------------------------------------------------
# HTML generation
# ---------------------------------------------------------------------------

def _load_template() -> str:
    return files("looper_dashboard.templates").joinpath("monocart.html").read_text()


def generate_html(
    builds: list[dict],
    fetched_at: str,
    days: int,
    ai_analysis: list[dict] | None = None,
) -> str:
    rows_json = [
        {
            "jobName":      b["jobName"],
            "flowName":     b["flowName"],
            "buildNumber":  b.get("buildNumber"),
            "buildStatus":  b.get("buildStatus", "UNKNOWN"),
            "startTime":    b.get("startTime"),
            "duration":     b.get("duration"),
            "testsPassed":  b.get("testsPassed"),
            "testsFailed":  b.get("testsFailed"),
            "testsFlaky":   b.get("testsFlaky"),
            "testsSkipped": b.get("testsSkipped"),
            "reportUrl":    b.get("reportUrl", ""),
            "failedTitles": b.get("_failedTitles", []),
            "flakyTitles":  b.get("_flakyTitles",  []),
        }
        for b in builds
    ]

    template = _load_template()
    html = template.replace("__ROWS_JSON__",    json.dumps(rows_json))
    html = html.replace("__FETCHED_AT__",       fetched_at)
    html = html.replace("__DAYS__",             str(days))
    html = html.replace("__AI_ANALYSIS_JSON__", json.dumps(ai_analysis or []))
    return html


# ---------------------------------------------------------------------------
# Failure summary
# ---------------------------------------------------------------------------

def save_failure_summary(builds: list[dict], output_path: Path) -> None:
    seen: set[tuple[str, str]] = set()
    failures = []
    for b in builds:
        if b.get("buildStatus") != "FAILURE":
            continue
        key = (b["jobName"], b["flowName"])
        if key in seen:
            continue
        failed_titles = b.get("_failedTitles", [])
        flaky_titles  = b.get("_flakyTitles",  [])
        if not failed_titles and not flaky_titles:
            continue
        seen.add(key)
        failures.append({
            "job":         b["jobName"].split("/")[-1],
            "flow":        b["flowName"],
            "buildNumber": b.get("buildNumber"),
            "passed":      b.get("testsPassed",  0),
            "failed":      b.get("testsFailed",  0),
            "flaky":       b.get("testsFlaky",   0),
            "skipped":     b.get("testsSkipped", 0),
            "reportUrl":   b.get("reportUrl", ""),
            "failedTests": failed_titles,
            "flakyTests":  flaky_titles,
        })

    if failures:
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(json.dumps(failures, indent=2))
        print(f"Saved {len(failures)} failure summaries to {output_path}", file=sys.stderr)


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

def run(
    config: str,
    output: str,
    scm_url: str | None = None,
    days: int = DEFAULT_DAYS,
    no_details: bool = False,
    save_failures: str | None = None,
    analysis_json: str | None = None,
    auto_open: bool = False,
) -> bool:
    """Run the full monocart dashboard generation pipeline.

    Returns True on success.
    """
    print(f"Fetching last {days} days of monocart build history...", file=sys.stderr)
    result = batch_history(config_path=config, scm_url=scm_url, days=days)
    builds = result["builds"]
    fetched_at = result["fetchedAt"]

    jobs_seen = len({b["jobName"] for b in builds})
    print(f"   Found {len(builds)} builds across {jobs_seen} jobs", file=sys.stderr)

    if not no_details and builds:
        print(f"Fetching monocart summaries ({len(builds)} parallel requests)...", file=sys.stderr)
        builds = enrich_builds(builds)

    failures_path = Path(save_failures or "wpa-self-service-failures.json")
    save_failure_summary(builds, failures_path)

    ai_analysis = None
    if analysis_json:
        p = Path(analysis_json)
        if p.exists():
            ai_analysis = json.loads(p.read_text())
            print(f"Loaded AI analysis from {p}", file=sys.stderr)
        else:
            print(f"Warning: --analysis-json not found: {p}", file=sys.stderr)

    print("Generating monocart dashboard...", file=sys.stderr)
    html = generate_html(builds, fetched_at, days, ai_analysis=ai_analysis)

    output_path = Path(output)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    output_path.write_text(html)
    print(f"Dashboard saved to {output_path.resolve()}", file=sys.stderr)

    if auto_open:
        if sys.platform == "darwin":
            os.system(f"open '{output_path}'")
        elif sys.platform == "win32":
            os.startfile(str(output_path))  # noqa: S606
        else:
            os.system(f"xdg-open '{output_path}'")

    return True
