"""Provenance reports (plan S4) — printable/exportable audit + difference reports.

Pure / QGIS-free so it unit-tests cleanly. Produces self-contained HTML (prints
well, opens in any browser) from the captured provenance tables. Works off a task
id, resolving the task's latest run; the dock saves the returned string to a file.
"""

from __future__ import annotations

import html
import json
from typing import Any, Dict, List, Optional


def _diff_model_summaries(*args, **kwargs):
    """Lazy import so this module loads standalone (tests) and inside the plugin."""
    try:
        from .model_summary_diff import diff_model_summaries
    except ImportError:  # standalone / path-loaded
        from model_summary_diff import diff_model_summaries
    return diff_model_summaries(*args, **kwargs)


def run_facts(manager, task_id: str) -> Optional[Dict[str, Any]]:
    """Flat manifest of a task's latest run (or None if it has never run)."""
    try:
        runs = manager.get_step_runs_for_task(task_id)
    except Exception:
        runs = []
    if not runs:
        return None
    step = runs[0]
    run = None
    try:
        run = manager.get_workflow_run(step.run_id)
    except Exception:
        run = None
    snapshot = None
    if step.model_snapshot_id:
        try:
            snapshot = manager.get_model_snapshot(step.model_snapshot_id)
        except Exception:
            snapshot = None
    return {
        "task_name": step.name,
        "engine_type": step.engine_type,
        "engine_version": step.engine_version,
        "status": step.status,
        "rows_processed": step.rows_processed,
        "duration_ms": step.duration_ms,
        "when": step.start_time or step.created_at,
        "result_id": step.result_id,
        "run_kind": run.run_kind if run else "",
        "scenario_id": run.scenario_id if run else "",
        "user": run.created_by if run else "",
        "emds_version": run.emds_version if run else "",
        "gis": " ".join(p for p in ((run.gis_environment, run.gis_version) if run else ("", "")) if p),
        "model_name": snapshot.display_name if snapshot else "",
        "model_hash": snapshot.content_sha256 if snapshot else "",
        "model_schema_version": snapshot.schema_version if snapshot else "",
        "model_authoring_tool": snapshot.authoring_tool if snapshot else "",
        "model_summary": snapshot.model_summary if snapshot else None,
        "registry_id": (getattr(snapshot, "registry_id", "") if snapshot else "") or "",
        "registry_version": (getattr(snapshot, "registry_version", "") if snapshot else "") or "",
        "parameters": step.parameters,
        "field_mappings": step.field_mappings,
        "input_refs": step.input_refs,
    }


def _esc(value: Any) -> str:
    return html.escape("" if value is None else str(value))


def _kv_table(rows: List[tuple]) -> str:
    cells = "".join(
        f"<tr><th style='text-align:left;padding:2px 12px 2px 0;vertical-align:top'>{_esc(k)}</th>"
        f"<td style='padding:2px 0'>{_esc(v)}</td></tr>"
        for k, v in rows
    )
    return f"<table>{cells}</table>"


def _json_block(label: str, obj: Any) -> str:
    try:
        text = json.dumps(obj, indent=2, default=str)
    except Exception:
        text = str(obj)
    return f"<h3>{_esc(label)}</h3><pre style='background:#f6f8fa;padding:8px;border-radius:4px'>{_esc(text)}</pre>"


_HTML_HEAD = (
    "<!doctype html><html><head><meta charset='utf-8'>"
    "<style>body{font-family:Segoe UI,Arial,sans-serif;margin:24px;color:#222}"
    "h1{font-size:18px}h2{font-size:15px;border-bottom:1px solid #ddd;padding-bottom:4px}"
    "h3{font-size:13px;margin-bottom:4px}.muted{color:#666;font-size:12px}</style></head><body>"
)
_HTML_TAIL = "</body></html>"


def _run_section(facts: Dict[str, Any], title: str) -> str:
    rows = [
        ("Task", facts["task_name"]),
        ("Engine", f"{facts['engine_type']}  (v{facts['engine_version'] or '?'})"),
        ("Run kind", facts["run_kind"] or "baseline"),
        ("Status", facts["status"]),
        ("When", facts["when"]),
        ("Rows processed", facts["rows_processed"]),
        ("User", facts["user"]),
        ("EMDS / GIS", f"{facts['emds_version']} / {facts['gis']}"),
        ("Model", facts["model_name"] or "(none)"),
        ("Model hash", facts["model_hash"]),
        ("Model schema / tool", f"{facts['model_schema_version']} / {facts['model_authoring_tool']}"),
    ]
    if facts.get("registry_id"):
        rows.append(("Verified registry model", "Yes"))
        rows.append(("Registry model id", facts["registry_id"]))
        rows.append(("Registry version", facts.get("registry_version") or "(current)"))
    return (
        f"<h2>{_esc(title)}</h2>{_kv_table(rows)}"
        + _json_block("Parameters", facts["parameters"])
        + _json_block("Field mappings", facts["field_mappings"])
        + _json_block("Inputs", facts["input_refs"])
    )


def build_run_report_html(manager, task_id: str) -> str:
    """Full provenance/audit report for a task's latest run."""
    facts = run_facts(manager, task_id)
    if facts is None:
        return _HTML_HEAD + "<h1>Provenance report</h1><p>No runs recorded for this task yet.</p>" + _HTML_TAIL
    return (
        _HTML_HEAD
        + f"<h1>Provenance report — {_esc(facts['task_name'])}</h1>"
        + "<p class='muted'>What was done in the latest run of this task.</p>"
        + _run_section(facts, "Run")
        + _HTML_TAIL
    )


def _diff_lists(label: str, a: Any, b: Any) -> str:
    same = json.dumps(a, sort_keys=True, default=str) == json.dumps(b, sort_keys=True, default=str)
    badge = "<span class='muted'>(identical)</span>" if same else "<b>(changed)</b>"
    return (
        f"<h3>{_esc(label)} {badge}</h3>"
        + (("" if same else _json_block("A", a) + _json_block("B", b)) or "")
    )


def build_difference_report_html(manager, task_a_id: str, task_b_id: str) -> str:
    """Difference report between the latest runs of two tasks (model + config)."""
    a = run_facts(manager, task_a_id)
    b = run_facts(manager, task_b_id)
    if a is None or b is None:
        return _HTML_HEAD + "<h1>Difference report</h1><p>Both tasks must have been run.</p>" + _HTML_TAIL

    parts = [
        _HTML_HEAD,
        f"<h1>Difference report</h1><p class='muted'>{_esc(a['task_name'])} vs {_esc(b['task_name'])}</p>",
        _run_section(a, f"A — {a['task_name']}"),
        _run_section(b, f"B — {b['task_name']}"),
        "<h2>Differences</h2>",
    ]

    if a["model_hash"] != b["model_hash"]:
        parts.append(f"<p><b>Model changed:</b> {_esc(a['model_hash'][:12])}… → {_esc(b['model_hash'][:12])}…</p>")
        if a["model_summary"] and b["model_summary"]:
            diff = _diff_model_summaries(a["model_summary"], b["model_summary"], field_mappings=b["field_mappings"])
            sections = []
            for sec in diff.get("sections", []):
                counts = (len(sec["added"]), len(sec["removed"]), len(sec["renamed"]), len(sec["changed"]))
                if any(counts):
                    sections.append(f"<li>{_esc(sec['label'])}: +{counts[0]} −{counts[1]} ~{counts[2]} Δ{counts[3]}</li>")
            if diff.get("mapping_breakage"):
                names = ", ".join(_esc(m["target_variable"]) for m in diff["mapping_breakage"])
                sections.append(f"<li><b>Mapping breakage:</b> {names}</li>")
            if sections:
                parts.append("<ul>" + "".join(sections) + "</ul>")
    else:
        parts.append("<p class='muted'>Model: identical.</p>")

    parts.append(_diff_lists("Parameters", a["parameters"], b["parameters"]))
    parts.append(_diff_lists("Field mappings", a["field_mappings"], b["field_mappings"]))
    parts.append(_HTML_TAIL)
    return "".join(parts)
