# MIT License — Copyright (c) 2026 cvemula1
# Compact CI / PR-friendly summary renderer for NHInsight

from __future__ import annotations

import os
import sys
from typing import TextIO

from nhinsight.core.models import Classification, ScanResult, Severity

# ── ASCII-safe helpers ─────────────────────────────────────────────────

def is_ci() -> bool:
    """Detect if running inside a CI environment."""
    return any(os.environ.get(v) for v in ("CI", "GITHUB_ACTIONS", "GITLAB_CI", "JENKINS_URL"))


# Mapping: emoji → ASCII-safe fallback
_SEVERITY_ICON = {
    Severity.CRITICAL: "🔴",
    Severity.HIGH: "🟠",
    Severity.MEDIUM: "🟡",
    Severity.LOW: "🔵",
    Severity.INFO: "🟢",
}

_SEVERITY_ASCII = {
    Severity.CRITICAL: "[CRITICAL]",
    Severity.HIGH: "[HIGH]",
    Severity.MEDIUM: "[MEDIUM]",
    Severity.LOW: "[LOW]",
    Severity.INFO: "[INFO]",
}


def sev_icon(sev: Severity, *, ascii_safe: bool = False) -> str:
    """Return severity icon, ASCII-safe if requested."""
    if ascii_safe:
        return _SEVERITY_ASCII.get(sev, sev.value.upper())
    return _SEVERITY_ICON.get(sev, sev.value.upper())


def sev_badge(sev: Severity) -> str:
    """Markdown-friendly severity badge for tables."""
    return f"**{sev.value.upper()}**"


# ── Compact CI summary ────────────────────────────────────────────────

def print_ci_summary(
    result: ScanResult,
    ap_result=None,
    out: TextIO = sys.stdout,
    *,
    ascii_safe: bool = False,
) -> None:
    """Print a compact markdown summary suitable for $GITHUB_STEP_SUMMARY or PR comments.

    Parameters
    ----------
    result : ScanResult
        The scan result to summarize.
    ap_result : AttackPathResult | None
        Optional attack path results to include.
    out : TextIO
        Output stream.
    ascii_safe : bool
        If True, avoid emoji/unicode that breaks in some CI terminals.
    """
    nhis = [i for i in result.identities if i.classification != Classification.HUMAN]
    humans = [i for i in result.identities if i.classification == Classification.HUMAN]

    crit = sum(1 for i in nhis if i.highest_severity == Severity.CRITICAL)
    high = sum(1 for i in nhis if i.highest_severity == Severity.HIGH)
    med = sum(1 for i in nhis if i.highest_severity == Severity.MEDIUM)
    low = sum(1 for i in nhis if i.highest_severity == Severity.LOW)
    ok = sum(1 for i in nhis if i.highest_severity == Severity.INFO)

    # ── Header
    out.write("## NHInsight Scan Summary\n\n")

    if result.providers_scanned:
        out.write(f"**Providers:** {', '.join(result.providers_scanned)}  \n")
    scanned_str = f"**Identities scanned:** {len(nhis)} NHIs"
    if humans:
        scanned_str += f" + {len(humans)} humans"
    out.write(f"{scanned_str}  \n\n")

    # ── Severity table
    out.write("| Severity | Count |\n")
    out.write("|----------|-------|\n")
    out.write(f"| {_sev_cell('CRITICAL', crit, ascii_safe)} | {crit} |\n")
    out.write(f"| {_sev_cell('HIGH', high, ascii_safe)} | {high} |\n")
    out.write(f"| {_sev_cell('MEDIUM', med, ascii_safe)} | {med} |\n")
    out.write(f"| {_sev_cell('LOW', low, ascii_safe)} | {low} |\n")
    out.write(f"| {_sev_cell('Healthy', ok, ascii_safe)} | {ok} |\n\n")

    # ── Top findings (critical + high, limit 8)
    top_findings = []
    for ident in nhis:
        for flag in ident.risk_flags:
            if flag.severity in (Severity.CRITICAL, Severity.HIGH):
                top_findings.append((flag.severity, ident, flag))
    top_findings.sort(key=lambda x: (0 if x[0] == Severity.CRITICAL else 1))
    top_findings = top_findings[:8]

    if top_findings:
        out.write("### Top Findings\n\n")
        out.write("| Identity | Provider | Severity | Issue |\n")
        out.write("|----------|----------|----------|-------|\n")
        for sev, ident, flag in top_findings:
            out.write(
                f"| `{ident.name}` "
                f"| {ident.provider.value} "
                f"| {sev.value.upper()} "
                f"| {flag.message} |\n"
            )
        out.write("\n")

    # ── Privilege escalation paths
    if ap_result and ap_result.paths:
        _write_path_section(ap_result, out, ascii_safe=ascii_safe)

    # ── Immediate actions
    actions = _build_actions(nhis, ap_result)
    if actions:
        out.write("### Immediate Actions\n\n")
        for i, action in enumerate(actions[:5], 1):
            out.write(f"{i}. {action}\n")
        out.write("\n")

    out.write("---\n")
    out.write("*Generated by [NHInsight](https://github.com/cvemula1/NHInsight)*\n")


def _sev_cell(label: str, count: int, ascii_safe: bool) -> str:
    """Format a severity cell, bold if count > 0."""
    if count > 0:
        return f"**{label}**"
    return label


def _write_path_section(ap_result, out: TextIO, *, ascii_safe: bool = False) -> None:
    """Write privilege escalation paths in reviewer-friendly format."""
    paths = ap_result.paths
    out.write("### Privilege Escalation Paths\n\n")

    cross_sym = "(cross-system)" if ascii_safe else "⚡"

    out.write("| Path | Severity | Risk | Entry → Target | Fix |\n")
    out.write("|------|----------|------|----------------|-----|\n")

    for path in paths[:10]:
        sev = path.severity.value.upper()
        risk = f"{path.blast_radius:.0f}/100"
        entry = path.steps[0].node_label if path.steps else "?"
        target = path.steps[-1].node_label if path.steps else "?"
        cross = f" {cross_sym}" if path.cross_system else ""
        rec = (path.recommendation or "Review permissions.")[:80]
        out.write(f"| {path.id} | {sev} | {risk} | `{entry}` → `{target}`{cross} | {rec} |\n")

    if len(paths) > 10:
        out.write(f"\n*...and {len(paths) - 10} more paths*\n")
    out.write("\n")

    # Detailed reviewer-friendly blocks for critical paths (limit 3)
    crit_paths = [p for p in paths if p.severity == Severity.CRITICAL][:3]
    if crit_paths:
        out.write("<details>\n<summary><b>Critical path details</b></summary>\n\n")
        for path in crit_paths:
            _write_path_detail(path, out, ascii_safe=ascii_safe)
        out.write("</details>\n\n")


def _write_path_detail(path, out: TextIO, *, ascii_safe: bool = False) -> None:
    """Write a single path in reviewer-friendly format."""
    sev = path.severity.value.upper()
    entry = path.steps[0].node_label if path.steps else "?"
    target = path.steps[-1].node_label if path.steps else "?"

    out.write(f"**{sev}** — `{entry}` can reach `{target}`\n\n")

    # Path chain
    chain = " → ".join(f"`{s.node_label}`" for s in path.steps)
    out.write(f"**Path:** {chain}\n\n")

    # Why it matters
    why = _why_it_matters(path)
    out.write(f"**Why it matters:** {why}\n\n")

    # Fix
    out.write(f"**Fix:** {path.recommendation or 'Review and reduce permissions along this path.'}\n\n")

    out.write("---\n\n")


def _why_it_matters(path) -> str:
    """Generate a plain-English 'why it matters' sentence for a path."""
    entry = path.steps[0] if path.steps else None
    target = path.steps[-1] if path.steps else None

    if not entry or not target:
        return "This path reaches a privileged resource."

    parts = []

    # Cross-system warning
    if path.cross_system:
        providers = path.providers_involved
        parts.append(f"This path crosses system boundaries ({' → '.join(providers)})")

    # Entry point type
    entry_type = entry.node_type
    if "key" in entry_type or "secret" in entry_type:
        parts.append("A leaked credential could let an attacker traverse this entire chain")
    elif "service_account" in entry_type:
        parts.append("A compromised workload using this service account could escalate privileges")
    else:
        parts.append("Compromise of the entry point could lead to privilege escalation")

    # Target privilege
    target_label = target.node_label.lower()
    if "admin" in target_label or "owner" in target_label:
        parts.append("reaching full administrative control")
    elif "contributor" in target_label or "editor" in target_label:
        parts.append("reaching broad resource modification permissions")
    elif "cluster-admin" in target_label:
        parts.append("reaching full Kubernetes cluster control")
    else:
        parts.append("reaching a privileged target")

    # Risk score context
    if path.blast_radius >= 80:
        parts.append(f"Risk score {path.blast_radius:.0f}/100 — immediate remediation recommended")
    elif path.blast_radius >= 60:
        parts.append(f"Risk score {path.blast_radius:.0f}/100 — high-priority fix")

    return ". ".join(parts) + "."


def _build_actions(nhis, ap_result) -> list[str]:
    """Build a prioritized list of immediate actions."""
    actions = []

    # From findings
    for ident in nhis:
        for flag in ident.risk_flags:
            if flag.severity == Severity.CRITICAL:
                detail = flag.detail or flag.message
                # Strip severity prefix
                for prefix in ("Critical: ", "High: ", "Medium: "):
                    if detail.startswith(prefix):
                        detail = detail[len(prefix):]
                        break
                action = detail.split(". ")[0]
                if action and action[0].islower():
                    action = action[0].upper() + action[1:]
                actions.append(f"**{ident.name}** — {action}")

    # From attack paths
    if ap_result:
        for path in ap_result.paths:
            if path.severity == Severity.CRITICAL and path.recommendation:
                entry = path.steps[0].node_label if path.steps else "?"
                rec = path.recommendation.split(". ")[0]
                action_str = f"**{path.id}** ({entry}) — {rec}"
                if action_str not in actions:
                    actions.append(action_str)

    return actions[:5]


# ── GitHub Step Summary helper ─────────────────────────────────────────

def write_github_step_summary(
    result: ScanResult,
    ap_result=None,
    *,
    ascii_safe: bool = True,
) -> bool:
    """Write compact summary to $GITHUB_STEP_SUMMARY if available.

    Returns True if written, False if not in GitHub Actions.
    """
    summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
    if not summary_path:
        return False

    with open(summary_path, "a") as f:
        print_ci_summary(result, ap_result, out=f, ascii_safe=ascii_safe)

    return True
