"""Karate dashboard generator.

Fetches build history, enriches each build with karate-summary-json.txt and
per-feature failure detail (in parallel), renders and saves the HTML dashboard.
"""
import json
import os
import sys
from concurrent.futures import ThreadPoolExecutor, as_completed
from importlib.resources import files
from pathlib import Path

import requests

from .karate_ops import batch_history


DEFAULT_DAYS = 14


# ---------------------------------------------------------------------------
# Karate summary fetching  (requests replaces curl)
# ---------------------------------------------------------------------------

def _http_get_json(url: str) -> dict | None:
    try:
        resp = requests.get(url, timeout=10)
        if resp.status_code != 200 or not resp.text.strip():
            return None
        return resp.json()
    except (requests.RequestException, ValueError):
        return None


def _http_get_text(url: str) -> str | None:
    try:
        resp = requests.get(url, timeout=10)
        if resp.status_code != 200 or not resp.text.strip():
            return None
        return resp.text
    except requests.RequestException:
        return None


def _parse_feature_failures(feature_json: dict) -> list[dict]:
    failures = []
    for scenario in feature_json.get("scenarioResults", []):
        if not scenario.get("failed"):
            continue
        failed_step = error_message = http_status = None
        for step in scenario.get("stepResults", []):
            result = step.get("result", {})
            if result.get("status") == "failed":
                failed_step = step.get("step", {}).get("text", "")
                error_message = result.get("errorMessage", "")
                for log_entry in step.get("stepLog", []):
                    if isinstance(log_entry, str) and "status code" in log_entry.lower():
                        http_status = log_entry.strip()
                break
        if error_message and len(error_message) > 500:
            error_message = error_message[:500] + "..."
        failures.append({
            "scenario":     scenario.get("name", "Unknown"),
            "tags":         [t if isinstance(t, str) else t.get("name", "") for t in scenario.get("tags", [])],
            "failedStep":   failed_step or "Unknown step",
            "errorMessage": error_message or "No error message",
            "httpStatus":   http_status,
        })
    return failures


def enrich_builds(builds: list[dict]) -> list[dict]:
    """Fetch karate summaries + per-feature failure detail for all builds in parallel."""

    def _fetch_summary(build: dict) -> tuple[dict, dict | None]:
        return build, _http_get_json(build["reportJsonUrl"])

    with ThreadPoolExecutor(max_workers=16) as pool:
        futures = {pool.submit(_fetch_summary, b): b for b in builds if b.get("reportJsonUrl")}
        for future in as_completed(futures):
            build, summary = future.result()
            if summary:
                build["scenariosPassed"]  = summary.get("scenariosPassed", 0)
                build["scenariosFailed"]  = summary.get("scenariosfailed", 0)
                build["featuresPassed"]   = summary.get("featuresPassed", 0)
                build["featuresFailed"]   = summary.get("featuresFailed", 0)
                build["totalTime"]        = summary.get("elapsedTime", "")
                build["resultDate"]       = summary.get("resultDate", "")
                build["_featureSummary"]  = summary.get("featureSummary", [])
            else:
                build.setdefault("scenariosPassed", None)
                build.setdefault("scenariosFailed", None)
                build.setdefault("featuresPassed",  None)
                build.setdefault("featuresFailed",  None)
                build.setdefault("totalTime",       "")
                build.setdefault("resultDate",      "")
                build.setdefault("_featureSummary", [])

    fetch_tasks: list[tuple[dict, str, str]] = []
    for build in builds:
        if build.get("buildStatus") != "FAILURE":
            continue
        base_url = build["reportUrl"].rsplit("/", 1)[0]
        for feat in build.get("_featureSummary", []):
            if not feat.get("failed"):
                continue
            pkg = feat.get("packageQualifiedName", "")
            if not pkg:
                continue
            fetch_tasks.append((build, feat.get("name", pkg), f"{base_url}/{pkg}.karate-json.txt"))

    feature_results: dict[int, list[dict]] = {}

    def _do_fetch(task: tuple) -> tuple[int, str, list[dict]]:
        build, feat_name, url = task
        raw = _http_get_text(url)
        if not raw:
            return id(build), feat_name, []
        try:
            fj = json.loads(raw)
        except json.JSONDecodeError:
            return id(build), feat_name, []
        return id(build), feat_name, _parse_feature_failures(fj)

    if fetch_tasks:
        with ThreadPoolExecutor(max_workers=8) as pool:
            futures2 = [pool.submit(_do_fetch, t) for t in fetch_tasks]
            for future in as_completed(futures2):
                bid, feat_name, failures = future.result()
                feature_results.setdefault(bid, [])
                if failures:
                    feature_results[bid].append({"featureName": feat_name, "failures": failures})

    for build in builds:
        build["failureAnalysis"] = feature_results.get(id(build), [])

    return builds


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

def _load_template() -> str:
    return files("looper_dashboard.templates").joinpath("karate.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"),
            "scenariosPassed": b.get("scenariosPassed"),
            "scenariosFailed": b.get("scenariosFailed"),
            "featuresPassed":  b.get("featuresPassed"),
            "featuresFailed":  b.get("featuresFailed"),
            "totalTime":       b.get("totalTime", ""),
            "resultDate":      b.get("resultDate", ""),
            "reportUrl":       b.get("reportUrl", ""),
            "failureAnalysis": b.get("failureAnalysis", []),
        }
        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] = set()
    failures = []
    for b in sorted(builds, key=lambda x: x.get("startTime") or "", reverse=True):
        key = (b["jobName"], b["flowName"])
        if key in seen or b.get("buildStatus") != "FAILURE":
            continue
        analysis = b.get("failureAnalysis", [])
        total_scenarios = sum(len(f["failures"]) for f in analysis)
        seen.add(key)
        failures.append({
            "job":             b["jobName"].split("/")[-1],
            "flow":            b["flowName"],
            "buildNumber":     b.get("buildNumber"),
            "passed":          b.get("scenariosPassed", 0),
            "failed":          b.get("scenariosFailed", 0),
            "failedScenarios": total_scenarios,
            "features": [
                {
                    "name": feat["featureName"],
                    "failures": [
                        {"scenario": f["scenario"], "step": f["failedStep"], "error": f["errorMessage"][:200]}
                        for f in feat["failures"]
                    ],
                }
                for feat in analysis
            ],
        })

    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 Karate dashboard generation pipeline.

    Returns True on success.
    """
    print(f"Fetching last {days} days of Karate 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:
        print(f"Fetching Karate summaries ({len(builds)} parallel requests)...", file=sys.stderr)
        builds = enrich_builds(builds)

    failures_path = Path(save_failures or "karate-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 Karate 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
