import json
import os
import re
from datetime import datetime
from pathlib import Path
from urllib.parse import urlparse


# ============================================================
# 🔍 FIND CALLER PROJECT ROOT (NOT PACKAGE ROOT)
# ============================================================
def find_execution_root() -> Path:
    """
    Resolve project root based on WHERE tests are executed from,
    not where this package lives.
    """
    current = Path.cwd().resolve()

    while current != current.parent:
        if any(
            (current / marker).exists()
            for marker in (
                "pyproject.toml",
                "setup.py",
                "requirements.txt",
                ".git",
            )
        ):
            return current
        current = current.parent

    # Fallback: use cwd directly
    return Path.cwd().resolve()


PROJECT_ROOT = find_execution_root()
REPORTS_DIR = PROJECT_ROOT / "ai_swarm_reports"


# ============================================================
# 🧹 UTILS
# ============================================================
def extract_endpoint_name(method: str, url: str) -> str:
    """
    POST https://preprod-api.getepichome.in/api/validate-gst/
    -> POST_validate-gst
    """
    parsed = urlparse(url)
    parts = [p for p in parsed.path.split("/") if p]

    endpoint = parts[-1] if parts else "root"
    endpoint = re.sub(r"[^a-zA-Z0-9_-]", "-", endpoint)

    return f"{method}_{endpoint}"


# ============================================================
# 📝 REPORT WRITER
# ============================================================
from ai_testing_swarm.core.config import AI_SWARM_SLA_MS
from ai_testing_swarm.reporting.dashboard import write_dashboard_index

SENSITIVE_HEADER_KEYS = {
    "authorization",
    "cookie",
    "set-cookie",
    "api-token",
    "api-secret-key",
    "x-api-key",
}


def _redact_headers(headers: dict) -> dict:
    if not isinstance(headers, dict):
        return headers
    out = {}
    for k, v in headers.items():
        if str(k).lower() in SENSITIVE_HEADER_KEYS and v is not None:
            out[k] = "***REDACTED***"
        else:
            out[k] = v
    return out


def _redact_results(results: list) -> list:
    redacted = []
    for r in results:
        r = dict(r)
        req = dict(r.get("request") or {})
        req["headers"] = _redact_headers(req.get("headers") or {})
        r["request"] = req
        redacted.append(r)
    return redacted


def _markdown_escape(s: str) -> str:
    return str(s).replace("`", "\\`")


def _render_markdown(report: dict) -> str:
    lines: list[str] = []
    lines.append(f"# AI Testing Swarm Report")
    lines.append("")
    lines.append(f"**Endpoint:** `{_markdown_escape(report.get('endpoint'))}`  ")
    lines.append(f"**Run time:** `{_markdown_escape(report.get('run_time'))}`  ")
    lines.append(f"**Total tests:** `{report.get('total_tests')}`")

    meta = report.get("meta") or {}
    if meta.get("gate_status"):
        lines.append(f"**Gate:** `{_markdown_escape(meta.get('gate_status'))}`  ")
    if meta.get("endpoint_risk_score") is not None:
        lines.append(f"**Endpoint risk:** `{_markdown_escape(meta.get('endpoint_risk_score'))}`")

    lines.append("")

    # Batch2: trend
    trend = report.get("trend") or {}
    if trend.get("has_previous"):
        lines.append("## Trend vs previous run")
        lines.append("")
        lines.append(f"- **Endpoint risk delta:** `{trend.get('endpoint_risk_delta')}` (prev={trend.get('endpoint_risk_prev')})")
        lines.append(f"- **Regressions:** `{trend.get('regression_count')}`")
        regs = trend.get("regressions") or []
        if regs:
            lines.append("")
            lines.append("### Regressed tests")
            for x in regs[:10]:
                lines.append(
                    "- "
                    f"`{_markdown_escape(x.get('name'))}`: "
                    f"{_markdown_escape(x.get('prev_status'))}→{_markdown_escape(x.get('curr_status'))} "
                    f"(risk {x.get('prev_risk_score')}→{x.get('curr_risk_score')})"
                )
        lines.append("")

    summary = report.get("summary") or {}
    counts_ft = summary.get("counts_by_failure_type") or {}
    counts_sc = summary.get("counts_by_status_code") or {}

    lines.append("## Summary")
    lines.append("")
    lines.append("### Counts by failure type")
    for k, v in sorted(counts_ft.items(), key=lambda kv: (-kv[1], kv[0])):
        lines.append(f"- **{_markdown_escape(k)}**: {v}")

    lines.append("")
    lines.append("### Counts by status code")
    for k, v in sorted(counts_sc.items(), key=lambda kv: (-kv[1], kv[0])):
        lines.append(f"- **{_markdown_escape(k)}**: {v}")

    # Risky findings
    results = report.get("results") or []
    risky = [r for r in results if str(r.get("status")) == "RISK"]
    failed = [r for r in results if str(r.get("status")) == "FAILED"]

    lines.append("")
    lines.append("## Top risky findings")
    if not risky and not failed:
        lines.append("- (none)")
    else:
        top = (risky + failed)[:10]
        for r in top:
            resp = r.get("response") or {}
            sc = resp.get("status_code")
            lines.append(
                f"- **{_markdown_escape(r.get('status'))}** `{_markdown_escape(r.get('name'))}` "
                f"(risk={_markdown_escape(r.get('risk_score'))}, status={sc}, "
                f"failure_type={_markdown_escape(r.get('failure_type'))})"
            )

    return "\n".join(lines) + "\n"


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


def _render_html(report: dict) -> str:
    endpoint = _html_escape(report.get("endpoint"))
    run_time = _html_escape(report.get("run_time"))
    total_tests = report.get("total_tests")
    summary = report.get("summary") or {}
    meta = report.get("meta") or {}

    results = report.get("results") or []
    risky = [r for r in results if str(r.get("status")) == "RISK"]
    failed = [r for r in results if str(r.get("status")) == "FAILED"]
    top_risky = (risky + failed)[:10]

    trend = report.get("trend") or {}
    trend_html = ""
    if trend.get("has_previous"):
        regs = trend.get("regressions") or []
        items = "".join(
            "<li><code>{}</code>: {}→{} (risk {}→{})</li>".format(
                _html_escape(x.get("name")),
                _html_escape(x.get("prev_status")),
                _html_escape(x.get("curr_status")),
                _html_escape(x.get("prev_risk_score")),
                _html_escape(x.get("curr_risk_score")),
            )
            for x in regs[:10]
        ) or "<li>(none)</li>"
        trend_html = (
            "<h2>Trend vs previous run</h2>"
            f"<div class='meta'><div><b>Endpoint risk delta:</b> <code>{_html_escape(trend.get('endpoint_risk_delta'))}</code> "
            f"(prev <code>{_html_escape(trend.get('endpoint_risk_prev'))}</code>)</div>"
            f"<div><b>Regressions:</b> <code>{_html_escape(trend.get('regression_count'))}</code></div></div>"
            f"<ul>{items}</ul>"
        )

    def _kv_list(d: dict) -> str:
        items = sorted((d or {}).items(), key=lambda kv: (-kv[1], kv[0]))
        return "".join(f"<li><b>{_html_escape(k)}</b>: {v}</li>" for k, v in items) or "<li>(none)</li>"

    def _pre(obj) -> str:
        try:
            txt = json.dumps(obj, indent=2, ensure_ascii=False)
        except Exception:
            txt = str(obj)
        return f"<pre>{_html_escape(txt)}</pre>"

    rows = []
    for r in results:
        resp = r.get("response") or {}
        issues = resp.get("openapi_validation") or []
        status = _html_escape(r.get("status"))
        name = _html_escape(r.get("name"))
        failure_type = _html_escape(r.get("failure_type"))
        sc = _html_escape(resp.get("status_code"))
        elapsed = _html_escape(resp.get("elapsed_ms"))
        risk_score = _html_escape(r.get("risk_score"))
        badge = {
            "PASSED": "badge passed",
            "FAILED": "badge failed",
            "RISK": "badge risk",
        }.get(r.get("status"), "badge")

        rows.append(
            "<details class='case'>"
            f"<summary><span class='{badge}'>{status}</span> "
            f"<b>{name}</b> — risk={risk_score}, status={sc}, elapsed_ms={elapsed}, failure_type={failure_type}"
            f"{(' — openapi_issues=' + str(len(issues))) if issues else ''}"
            "</summary>"
            "<div class='grid'>"
            f"<div><h4>Request</h4>{_pre(r.get('request'))}</div>"
            f"<div><h4>Response</h4>{_pre(resp)}</div>"
            f"<div><h4>Reasoning</h4>{_pre({k: r.get(k) for k in ('reason','confidence','failure_type','status','risk_score')})}</div>"
            "</div>"
            "</details>"
        )

    top_list = "".join(
        f"<li><b>{_html_escape(r.get('status'))}</b> {_html_escape(r.get('name'))}"
        f" (failure_type={_html_escape(r.get('failure_type'))})</li>"
        for r in top_risky
    ) or "<li>(none)</li>"

    css = """
    body{font-family:ui-sans-serif,system-ui,Segoe UI,Roboto,Arial; margin:20px;}
    .meta{color:#444;margin-bottom:16px}
    .grid{display:grid; grid-template-columns: 1fr 1fr 1fr; gap:12px; margin-top:10px}
    pre{background:#0b1020;color:#e6e6e6;padding:10px;border-radius:8px;overflow:auto;max-height:360px}
    details.case{border:1px solid #ddd; border-radius:10px; padding:10px; margin:10px 0}
    summary{cursor:pointer}
    .badge{display:inline-block; padding:2px 8px; border-radius:999px; font-size:12px; margin-right:8px; border:1px solid #aaa}
    .badge.passed{background:#e6ffed;border-color:#36b37e}
    .badge.failed{background:#ffebe6;border-color:#ff5630}
    .badge.risk{background:#fff7e6;border-color:#ffab00}
    """

    return f"""<!doctype html>
<html>
<head>
<meta charset='utf-8'/>
<title>AI Testing Swarm Report</title>
<style>{css}</style>
</head>
<body>
<h1>AI Testing Swarm Report</h1>
<div class='meta'>
  <div><b>Endpoint:</b> <code>{endpoint}</code></div>
  <div><b>Run time:</b> <code>{run_time}</code></div>
  <div><b>Total tests:</b> <code>{total_tests}</code></div>
  <div><b>Gate:</b> <code>{_html_escape(meta.get('gate_status'))}</code></div>
  <div><b>Endpoint risk:</b> <code>{_html_escape(meta.get('endpoint_risk_score'))}</code></div>
</div>

{trend_html}

<h2>Summary</h2>
<div class='grid'>
  <div><h3>Counts by failure type</h3><ul>{_kv_list(summary.get('counts_by_failure_type') or {})}</ul></div>
  <div><h3>Counts by status code</h3><ul>{_kv_list(summary.get('counts_by_status_code') or {})}</ul></div>
  <div><h3>Top risky findings</h3><ul>{top_list}</ul></div>
</div>

<h2>Results</h2>
{''.join(rows)}

</body>
</html>"""


def write_report(
    request: dict,
    results: list,
    *,
    meta: dict | None = None,
    report_format: str = "json",
    update_index: bool = True,
) -> str:
    """Write a swarm report.

    report_format:
      - json (default): full machine-readable report
      - md: human-readable markdown summary
      - html: single-file HTML report with collapsible sections
    """

    REPORTS_DIR.mkdir(exist_ok=True)

    method = request.get("method", "UNKNOWN")
    url = request.get("url", "")

    endpoint_name = extract_endpoint_name(method, url)

    # Batch2: optional run label (auth-matrix case, environment label, etc.)
    # This keeps reports for the same endpoint separated but still comparable.
    run_label = str((meta or {}).get("run_label") or "").strip()
    if run_label:
        safe = re.sub(r"[^a-zA-Z0-9_.-]", "-", run_label).strip("-")
        if safe:
            endpoint_name = f"{endpoint_name}__{safe}"

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")

    endpoint_dir = REPORTS_DIR / endpoint_name
    endpoint_dir.mkdir(parents=True, exist_ok=True)

    safe_results = _redact_results(results)

    # Batch2: load the previous JSON report for trend comparison (best-effort)
    previous_report = None
    try:
        from ai_testing_swarm.reporting.trend import compute_trend

        json_candidates = sorted(endpoint_dir.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True)
        prev_path = json_candidates[0] if json_candidates else None
        if prev_path and prev_path.exists():
            previous_report = json.loads(prev_path.read_text(encoding="utf-8"))
    except Exception:
        previous_report = None

    summary = {
        "counts_by_failure_type": {},
        "counts_by_status_code": {},
        "slow_tests": [],
        # Batch1: risk aggregation (optional fields)
        "endpoint_risk_score": 0,
        "top_risks": [],
    }

    for r in safe_results:
        ft = r.get("failure_type") or "unknown"
        summary["counts_by_failure_type"][ft] = summary["counts_by_failure_type"].get(ft, 0) + 1

        sc = (r.get("response") or {}).get("status_code")
        sc_key = str(sc)
        summary["counts_by_status_code"][sc_key] = summary["counts_by_status_code"].get(sc_key, 0) + 1

        elapsed = (r.get("response") or {}).get("elapsed_ms")
        if isinstance(elapsed, int) and elapsed > AI_SWARM_SLA_MS:
            summary["slow_tests"].append({"name": r.get("name"), "elapsed_ms": elapsed})

    # Batch1: endpoint risk summary (best-effort)
    try:
        scores = [r.get("risk_score") for r in safe_results if isinstance(r.get("risk_score"), int)]
        summary["endpoint_risk_score"] = int(max(scores)) if scores else 0
        top = sorted(
            (
                {
                    "name": r.get("name"),
                    "status": r.get("status"),
                    "failure_type": r.get("failure_type"),
                    "risk_score": r.get("risk_score"),
                }
                for r in safe_results
                if isinstance(r.get("risk_score"), int)
            ),
            key=lambda x: int(x.get("risk_score") or 0),
            reverse=True,
        )
        summary["top_risks"] = top[:10]
    except Exception:
        pass

    report = {
        "endpoint": f"{method} {url}",
        "run_time": timestamp,
        "total_tests": len(safe_results),
        "summary": summary,
        "meta": meta or {},
        "results": safe_results,
    }

    # Batch2: attach trend comparison (previous vs current)
    try:
        from ai_testing_swarm.reporting.trend import compute_trend

        report["trend"] = compute_trend(report, previous_report)
    except Exception:
        report["trend"] = {"has_previous": False, "regressions": [], "regression_count": 0}

    report_format = (report_format or "json").lower().strip()
    if report_format not in {"json", "md", "html"}:
        report_format = "json"

    ext = {"json": "json", "md": "md", "html": "html"}[report_format]
    report_path = endpoint_dir / f"{endpoint_name}_{timestamp}.{ext}"

    if report_format == "json":
        with open(report_path, "w", encoding="utf-8") as f:
            json.dump(report, f, indent=2)
    elif report_format == "md":
        with open(report_path, "w", encoding="utf-8") as f:
            f.write(_render_markdown(report))
    else:
        with open(report_path, "w", encoding="utf-8") as f:
            f.write(_render_html(report))

    # Batch1: update a simple static dashboard index.html
    if update_index:
        try:
            write_dashboard_index(REPORTS_DIR)
        except Exception:
            # Dashboard is best-effort; never fail the run
            pass

    return str(report_path)
