"""Notebook API for inventory & certificate operations.

CxO-friendly interface — hides subprocess calls, JSON parsing, plotly chart
building, and error handling behind clean function calls.

Usage in notebooks:
    from runbooks.notebooks.inventory_api import (
        run_cross_validate, run_cert_triage, run_resource_explorer,
        show_cert_expiry_chart, show_cert_risk_chart,
        show_accuracy_chart, show_v1_v2_chart,
        show_expiring_certs, show_removable_certs,
        generate_cxo_report, generate_stakeholder_email,
    )
"""

import json
import subprocess
from datetime import datetime
from pathlib import Path

import pandas as pd
import plotly.express as px
import plotly.graph_objects as go


# ── CLI Execution (subprocess wrappers) ───────────────────────


def run_cross_validate(
    profile: str,
    output_dir: str = "data/evidence/inventory",
    persona: str = "all",
    resource_type: str | None = None,
) -> dict:
    """Run inventory cross-validation and return parsed evidence dict.

    Args:
        profile: AWS SSO profile name (e.g. $AWS_OPERATIONS_PROFILE).
        output_dir: Directory for JSON evidence output.
        persona: Persona filter (default "all").
        resource_type: Optional single resource type to validate.

    Returns:
        dict with keys: per_type, overall_accuracy, overall_resources, timing, etc.

    Raises:
        RuntimeError: If CLI exits non-zero.
    """
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    cmd = [
        "runbooks",
        "inventory",
        "cross-validate",
        "--ops-profile",
        profile,
        "--persona",
        persona,
        "--export-json",
        "--verbose",
        "--output-dir",
        output_dir,
    ]
    if resource_type:
        cmd.extend(["--resource-type", resource_type])
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    if result.returncode != 0:
        raise RuntimeError(f"cross-validate failed (exit {result.returncode}): {result.stderr[:500]}")
    files = sorted(Path(output_dir).glob("cross-validate-*.json"))
    if not files:
        raise RuntimeError(f"No evidence JSON in {output_dir}")
    return json.loads(files[-1].read_text())


def run_cert_triage(
    profile: str,
    output_dir: str = "data/evidence/security",
    days: int = 90,
    mode: str = "executive",
    all_accounts: bool = True,
    email: bool = True,
) -> dict:
    """Run certificate triage and return parsed evidence dict.

    Args:
        profile: AWS SSO profile name.
        output_dir: Directory for JSON evidence output.
        days: Certificate expiry window in days.
        mode: Display mode (executive/sre/cto/cloudops).
        all_accounts: Scan all accounts in Landing Zone.
        email: Generate stakeholder email artifact.

    Returns:
        dict with key 'certificates' (list of cert dicts).

    Raises:
        RuntimeError: If CLI exits non-zero.
    """
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    json_path = str(Path(output_dir) / "cert-triage.json")
    cmd = [
        "runbooks",
        "cert",
        "triage",
        "--ops-profile",
        profile,
        "--days",
        str(days),
        "--mode",
        mode,
        "--export-json",
        json_path,
        "--output-dir",
        output_dir,
    ]
    if all_accounts:
        cmd.append("--all-accounts")
    if email:
        cmd.append("--email")
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
    if result.returncode != 0:
        raise RuntimeError(f"cert triage failed (exit {result.returncode}): {result.stderr[:500]}")
    if not Path(json_path).exists():
        raise RuntimeError(f"No cert evidence at {json_path}")
    return json.loads(Path(json_path).read_text())


def run_resource_explorer(
    profile: str,
    resource_type: str,
    output_dir: str = "data/evidence/inventory",
) -> pd.DataFrame:
    """Run resource-explorer and return DataFrame.

    Args:
        profile: AWS SSO profile name.
        resource_type: Resource type to discover (e.g. "ec2", "s3").
        output_dir: Directory for CSV output.

    Returns:
        pandas DataFrame with discovered resources.

    Raises:
        RuntimeError: If CLI exits non-zero.
    """
    Path(output_dir).mkdir(parents=True, exist_ok=True)
    csv_path = str(Path(output_dir) / f"{resource_type}-fleet.csv")
    cmd = [
        "runbooks",
        "inventory",
        "resource-explorer",
        "--resource-type",
        resource_type,
        "--profile",
        profile,
        "--output",
        csv_path,
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
    if result.returncode != 0:
        raise RuntimeError(f"resource-explorer failed (exit {result.returncode}): {result.stderr[:500]}")
    if not Path(csv_path).exists():
        raise RuntimeError(f"No CSV at {csv_path}")
    return pd.read_csv(csv_path)


# ── Certificate Charts ────────────────────────────────────────


def _safe_days(cert: dict) -> int:
    """Extract days_until_expiry, handling None and missing values."""
    raw = cert.get("days_until_expiry")
    if raw is None:
        raw = cert.get("days_to_expiry")
    return raw if raw is not None else 999


def show_cert_expiry_chart(certs: list[dict]) -> go.Figure:
    """Certificate expiry distribution bar chart for CxO dashboard.

    Args:
        certs: List of certificate dicts (from run_cert_triage).

    Returns:
        Plotly Figure ready for fig.show() or display in notebook.
    """
    buckets: dict[str, int] = {"EXPIRED": 0, "< 7d": 0, "< 30d": 0, "< 90d": 0, "> 90d": 0}
    for c in certs:
        d = _safe_days(c)
        if d <= 0:
            k = "EXPIRED"
        elif d <= 7:
            k = "< 7d"
        elif d <= 30:
            k = "< 30d"
        elif d <= 90:
            k = "< 90d"
        else:
            k = "> 90d"
        buckets[k] += 1
    df = pd.DataFrame({"Bucket": list(buckets.keys()), "Count": list(buckets.values())})
    fig = px.bar(
        df,
        x="Bucket",
        y="Count",
        color="Bucket",
        color_discrete_sequence=["#ef4444", "#f97316", "#f59e0b", "#3b82f6", "#22c55e"],
        title="Certificate Expiry Distribution (Live AWS)",
    )
    fig.update_layout(template="plotly_dark", showlegend=False)
    return fig


def show_cert_risk_chart(certs: list[dict]) -> go.Figure:
    """Certificate risk by account bar chart.

    Args:
        certs: List of certificate dicts (from run_cert_triage).

    Returns:
        Plotly Figure ready for fig.show() or display in notebook.
    """
    rows = []
    for c in certs:
        d = _safe_days(c)
        risk = "critical" if d <= 7 else "warning" if d <= 30 else "attention" if d <= 90 else "valid"
        rows.append(
            {
                "account": c.get("account_id", c.get("account_name", "?")),
                "risk": risk,
            }
        )
    if not rows:
        return px.bar(title="No certificates")
    df = pd.DataFrame(rows).groupby(["account", "risk"]).size().reset_index(name="count")
    fig = px.bar(
        df,
        x="account",
        y="count",
        color="risk",
        color_discrete_map={
            "critical": "#ef4444",
            "warning": "#f97316",
            "attention": "#3b82f6",
            "valid": "#22c55e",
        },
        title="Certificate Risk by Account",
    )
    fig.update_layout(template="plotly_dark", xaxis_tickangle=-45)
    return fig


# ── Inventory Charts ──────────────────────────────────────────


def show_accuracy_chart(data: dict) -> go.Figure:
    """Cross-validation accuracy bar chart with PASS/WARN thresholds.

    Args:
        data: Evidence dict from run_cross_validate.

    Returns:
        Plotly Figure ready for fig.show() or display in notebook.
    """
    rows = []
    for rtype, m in data.get("per_type", {}).items():
        rows.append(
            {
                "Type": rtype.upper(),
                "Accuracy": m.get("accuracy", 0),
                "Status": m.get("status", "N/A"),
            }
        )
    df = pd.DataFrame(rows)
    fig = px.bar(
        df,
        x="Type",
        y="Accuracy",
        color="Status",
        color_discrete_map={"PASS": "#22c55e", "WARN": "#f97316", "FAIL": "#ef4444"},
        title="Cross-Validation Accuracy by Resource Type (Live AWS)",
    )
    fig.add_hline(y=99.5, line_dash="dash", line_color="green", annotation_text="PASS (99.5%)")
    fig.add_hline(y=95.0, line_dash="dash", line_color="orange", annotation_text="WARN (95%)")
    fig.update_layout(yaxis_range=[85, 101], template="plotly_dark")
    return fig


def show_v1_v2_chart(data: dict) -> go.Figure:
    """V1 vs V2 resource count grouped bar chart.

    Args:
        data: Evidence dict from run_cross_validate.

    Returns:
        Plotly Figure ready for fig.show() or display in notebook.
    """
    types: list[str] = []
    v1s: list[int] = []
    v2s: list[int] = []
    for rtype, m in data.get("per_type", {}).items():
        types.append(rtype.upper())
        v1s.append(m.get("v1", 0))
        v2s.append(m.get("v2", 0))
    fig = go.Figure(
        data=[
            go.Bar(name="Config Aggregator (V1)", x=types, y=v1s, marker_color="#6366f1"),
            go.Bar(name="Resource Explorer (V2)", x=types, y=v2s, marker_color="#0ea5e9"),
        ]
    )
    fig.update_layout(
        barmode="group",
        title="V1 vs V2 Resource Count",
        template="plotly_dark",
    )
    return fig


# ── CxO Display Helpers ──────────────────────────────────────


def show_expiring_certs(certs: list[dict], days: int = 90, max_rows: int = 20) -> list[dict]:
    """Display expiring certificates table for CxO.

    Args:
        certs: List of certificate dicts (from run_cert_triage).
        days: Show certificates expiring within this many days.
        max_rows: Maximum rows to display.

    Returns:
        Filtered list of expiring certificate dicts.
    """
    from IPython.display import display  # type: ignore[import]

    expiring = [c for c in certs if 0 < _safe_days(c) <= days]
    if not expiring:
        print(f"No in-use certificates expiring within {days} days")
        return []
    df = pd.DataFrame(
        [
            {
                "Domain": c.get("domain_name", c.get("domain", "?")),
                "Days Left": _safe_days(c),
                "Account": c.get("account_name") or c.get("account_id", ""),
                "Region": c.get("region", ""),
                "Type": c.get("certificate_type", ""),
                "Auto-Renew": "Yes" if c.get("auto_renewal_eligible") else "No",
            }
            for c in sorted(expiring, key=_safe_days)[:max_rows]
        ]
    )
    print(f"{len(expiring)} certificates expiring within {days} days")
    display(df)
    return expiring


def show_removable_certs(certs: list[dict], max_rows: int = 20) -> list[dict]:
    """Display expired/removable certificates table for CxO.

    Args:
        certs: List of certificate dicts (from run_cert_triage).
        max_rows: Maximum rows to display.

    Returns:
        Filtered list of expired certificate dicts safe to remove.
    """
    from IPython.display import display  # type: ignore[import]

    removable = [c for c in certs if _safe_days(c) <= 0]
    if not removable:
        print("No expired unused certificates to clean up")
        return []
    df = pd.DataFrame(
        [
            {
                "Domain": c.get("domain_name", c.get("domain", "?")),
                "Account": c.get("account_name") or c.get("account_id", ""),
                "Expired": f"{abs(_safe_days(c))} days ago",
                "Region": c.get("region", ""),
            }
            for c in sorted(removable, key=_safe_days)[:max_rows]
        ]
    )
    print(f"{len(removable)} expired certificates safe to remove")
    display(df)
    return removable


# ── CxO Report Generation ────────────────────────────────────


def generate_cxo_report(data: dict, output_dir: str, name: str) -> dict:
    """Export evidence in CxO-friendly formats. Returns paths dict.

    Generates:
      - .csv  (opens in Excel)
      - .md   (renders in GitHub/Outlook)
      - .json (agent-consumable only — hidden from CxO display)

    Args:
        data: Evidence dict from run_cross_validate or run_cert_triage.
        output_dir: Directory to write exported files.
        name: Base filename (without extension).

    Returns:
        dict mapping format keys ("csv", "md", "json") to file paths.
    """
    from runbooks.notebooks.helpers import export_cxo_report

    return export_cxo_report(data, output_dir, name)


def generate_stakeholder_email(
    risk: str,
    total: int,
    accounts: int,
    expiring: list[dict],
    removable: list[dict],
    mode: str,
    evidence_paths: dict | None = None,
    profile: str = "",
    days: int = 90,
    output_dir: str = "",
) -> str:
    """Generate and save CxO stakeholder email with evidence references.

    Args:
        risk: Risk level string (CRITICAL/HIGH/MEDIUM/HEALTHY).
        total: Total certificate count.
        accounts: Number of accounts scanned.
        expiring: List of expiring certificate dicts.
        removable: List of expired+unused certificate dicts.
        mode: CLI mode used (executive/sre/cto/cloudops).
        evidence_paths: Optional paths dict from generate_cxo_report — non-JSON
                        paths listed as attachments; JSON is hidden from CxO.
        profile: AWS profile used (appended to reproduce command).
        days: Days window used (appended to reproduce command).
        output_dir: If set, saves email to {output_dir}/cert-email-YYYY-MM-DD.txt.

    Returns:
        Email string ready for copy-paste.
    """
    from runbooks.notebooks.helpers import build_email

    email = build_email(
        risk,
        total,
        accounts,
        expiring,
        removable,
        mode,
        evidence_paths=evidence_paths,
    )
    email += f"\nReproduce: runbooks cert triage --days {days} --mode {mode} --ops-profile {profile} --all-accounts\n"
    if output_dir:
        path = Path(output_dir) / f"cert-email-{datetime.now().strftime('%Y-%m-%d')}.txt"
        path.write_text(email)
        print(f"Email saved to {path}")
    return email


def print_summary(
    name: str,
    data: dict,
    output_dir: str,
    profile: str,
    days: int = 0,
    mode: str = "",
) -> None:
    """Print CxO-friendly one-pager summary with reproduce command.

    Args:
        name: Report title (e.g. "Certificate Triage" or "Cross-Validation").
        data: Evidence dict from run_cert_triage or run_cross_validate.
        output_dir: Output directory shown in summary footer.
        profile: AWS profile used (shown in reproduce command).
        days: Days window for cert triage (0 = omit from cert line).
        mode: CLI mode for cert triage (omit if empty).
    """
    separator = "=" * 60
    print(separator)
    print(f"  {name} — {datetime.now().strftime('%Y-%m-%d')}")
    print(separator)
    if "certificates" in data:
        certs = data["certificates"]
        exp = [c for c in certs if 0 < _safe_days(c) <= (days or 90)]
        rem = [c for c in certs if _safe_days(c) <= 0]
        print(f"  Total: {len(certs)} | Expiring: {len(exp)} | Removable: {len(rem)}")
        print(f"  Reproduce: runbooks cert triage --days {days} --mode {mode} --ops-profile {profile} --all-accounts")
    elif "per_type" in data:
        print(f"  Accuracy: {data.get('overall_accuracy', '?')}% | Resources: {data.get('overall_resources', '?')}")
        print(f"  Accounts: {data.get('v3_accounts', '?')} | Timing: {data.get('elapsed_seconds', '?')}s")
        print(f"  Reproduce: runbooks inventory cross-validate --ops-profile {profile} --persona all --export-json")
    print(f"  Output: {output_dir}/")
    print(separator)


def show_workflow_summary(output_dir: str, cert_json: str = "", ec2_csv: str = "") -> list:
    """Print workflow summary table from evidence files. Returns summary list."""
    summary = []
    # Cross-validation
    cv_files = sorted(Path(output_dir).glob("cross-validate*.json"))
    if cv_files:
        cv = json.loads(cv_files[-1].read_text())
        summary.append(("Cross-Validate", f"{cv.get('overall_accuracy', '?')}% accuracy, {cv.get('overall_resources', '?')} resources", str(cv_files[-1])))
    # Cert triage
    cert_path = Path(cert_json) if cert_json else Path(output_dir) / "cert-triage.json"
    if cert_path.exists():
        cd = json.loads(cert_path.read_text())
        total = cd.get("summary", {}).get("total_certificates", len(cd.get("certificates", [])))
        summary.append(("Cert Triage", f"{total} certificates scanned", str(cert_path)))
    # EC2
    ec2_path = Path(ec2_csv) if ec2_csv else Path(output_dir) / "ec2-fleet.csv"
    if ec2_path.exists():
        ec2_count = len(pd.read_csv(ec2_path))
        summary.append(("Resource Explorer EC2", f"{ec2_count} instances", str(ec2_path)))

    print(f"{'─' * 80}")
    print(f"{'WORKFLOW':<28} {'RESULT':<35} FILE")
    print(f"{'─' * 80}")
    for wf, result, path in summary:
        print(f"{wf:<28} {result:<35} {Path(path).name}")
    print(f"{'─' * 80}")
    return summary


def show_showback_table(services: list, accounts: list, profile: str = "", mode: str = "", timeframe: str = ""):
    """Display showback analysis tables for CxO."""
    from IPython.display import display

    def _assign_tier(cost):
        if cost is None: return "Unknown"
        return "Critical" if cost > 10000 else "High" if cost > 1000 else "Medium" if cost > 100 else "Low"

    def _fmt_cost(cost):
        return f"${cost:,.2f}" if isinstance(cost, (int, float)) else str(cost)

    if services:
        df = pd.DataFrame(services)
        cost_col = next((c for c in df.columns if "cost" in c.lower()), None)
        name_col = next((c for c in df.columns if "name" in c.lower() or "service" in c.lower()), None)
        if cost_col and name_col:
            df["Tier"] = df[cost_col].apply(_assign_tier)
            df["Cost (USD)"] = df[cost_col].apply(_fmt_cost)
            display(df[[name_col, "Cost (USD)", "Tier"]].style.set_caption("Showback by Service").hide(axis="index"))
    else:
        print("No service cost data — run: runbooks finops dashboard --profile " + profile)

    if accounts:
        adf = pd.DataFrame(accounts)
        cost_col = next((c for c in adf.columns if "cost" in c.lower()), None)
        name_col = next((c for c in adf.columns if "name" in c.lower() or "account" in c.lower()), None)
        if cost_col and name_col:
            adf["Tier"] = adf[cost_col].apply(_assign_tier)
            adf["Cost (USD)"] = adf[cost_col].apply(_fmt_cost)
            display(adf[[name_col, "Cost (USD)", "Tier"]].style.set_caption("Showback by Account").hide(axis="index"))
    else:
        print("No account data — authenticate first")
