"""Reusable notebook functions for runbooks PyPI (CxO-grade).

Usage in notebooks:
    from runbooks.notebooks.helpers import run_cli, validate_auth, assign_tier
"""

import json
import sys
import subprocess
from datetime import datetime
from pathlib import Path
from typing import Any


def _runbooks_cmd() -> str:
    """Resolve the Python runbooks CLI (not the Node.js ADLC binary)."""
    venv_bin = Path(sys.executable).parent / "runbooks"
    if venv_bin.exists():
        return str(venv_bin)
    return "runbooks"


def run_cli(cmd: list[str], export_json: Path | None = None, timeout: int = 120) -> dict[str, Any]:
    """Run a runbooks CLI command and return parsed JSON output.

    Args:
        cmd: CLI command as list (e.g., ["runbooks", "cert", "triage", ...])
        export_json: Path to JSON export file (if CLI writes one)
        timeout: Subprocess timeout in seconds

    Returns:
        Parsed JSON dict from export file, or empty structure on failure.
    """
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)

    if result.returncode == 0 and export_json and export_json.exists():
        with open(export_json) as f:
            return json.load(f)

    # Graceful failure — return empty structure, print actionable message
    if result.returncode != 0:
        err = result.stderr.strip().split("\n")[-1] if result.stderr else "Unknown error"
        print(f"CLI warning: {err}")
        print("Tip: Run 'aws sso login' on your host if credentials expired")

    return {"certificates": [], "summary": {"total_certificates": 0, "by_expiry_bucket": {}}, "scan_metadata": {}}


def risk_level(certs: list[dict]) -> str:
    """Determine risk level from certificate list."""
    in_use_expired = sum(1 for c in certs if c.get("expiry_bucket") == "EXPIRED" and c.get("in_use"))
    critical = sum(1 for c in certs if c.get("expiry_bucket") == "CRITICAL_7D")
    warning = sum(1 for c in certs if c.get("expiry_bucket") == "WARNING_30D")
    if in_use_expired:
        return "CRITICAL"
    if critical:
        return "HIGH"
    if warning:
        return "MEDIUM"
    return "HEALTHY"


def filter_expiring(certs: list[dict], days: int) -> list[dict]:
    """Filter and sort certificates expiring within N days (in-use only)."""
    expiring = [c for c in certs if 0 < (c.get("days_until_expiry") or 999) <= days and c.get("in_use")]
    expiring.sort(key=lambda c: c.get("days_until_expiry") or 999)
    return expiring


def filter_removable(certs: list[dict]) -> list[dict]:
    """Filter expired certificates that are not in use (safe to remove)."""
    return [c for c in certs if c.get("expiry_bucket") == "EXPIRED" and not c.get("in_use")]


def build_email(
    risk: str,
    total: int,
    accounts: int,
    expiring: list[dict],
    removable: list[dict],
    mode: str,
    evidence_paths: dict | None = None,
) -> str:
    """Build stakeholder email template from certificate data.

    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 dict from export_cxo_report() — non-JSON paths
                        listed as attachments. JSON is hidden from CxO email.
    """
    in_use_expired = sum(1 for c in expiring if (c.get("days_until_expiry") or 0) <= 0)
    warning = sum(1 for c in expiring if 0 < (c.get("days_until_expiry") or 999) <= 30)

    expiring_list = (
        "\n".join(
            f"  - {c['domain_name']} ({c.get('days_until_expiry', '?')}d, "
            f"{c.get('account_name') or c.get('account_id', '?')})"
            for c in expiring[:10]
        )
        or "  None"
    )

    removable_list = (
        "\n".join(
            f"  - {c['domain_name']} ({c.get('account_name') or c.get('account_id', '?')})" for c in removable[:10]
        )
        or "  None"
    )

    email = f"""Subject: Weekly Certificate Status — {risk}: {total} certs ({datetime.now().strftime("%d %b %Y")})

Dear Leadership Team,

Certificate status report for your review.

Risk Level: {risk}
Total Certificates: {total} | Accounts Scanned: {accounts}

Certificates Expiring Soon (action required):
{expiring_list}

Expired Certificates — Safe to Remove:
{removable_list}

Recommended Actions:
  1. Renew {in_use_expired} expired in-use certificates immediately
  2. Remove {len(removable)} expired unused certificates
  3. Schedule renewal for {warning} certificates expiring within 30 days

Generated via: runbooks cert triage --mode {mode}
"""

    if evidence_paths:
        email += "\nAttachments:\n"
        for fmt, path in evidence_paths.items():
            if fmt != "json":  # Don't reference JSON to CxO
                email += f"  - {Path(path).name} ({fmt.upper()})\n"

    return email


# ---------------------------------------------------------------------------
# Generic helper functions — reusable across all cloudops notebooks
# ---------------------------------------------------------------------------


def validate_auth(profile: str) -> bool:
    """Check if AWS SSO session is valid for the given profile.

    Returns True if authenticated, False with actionable message if not.
    """
    result = subprocess.run(
        ["aws", "sts", "get-caller-identity", "--profile", profile],
        capture_output=True,
        text=True,
        timeout=10,
    )
    if result.returncode == 0:
        identity = json.loads(result.stdout)
        print(f"Authenticated: {identity.get('Arn', 'unknown')}")
        return True
    print(f"Authentication failed for profile '{profile}'")
    print("Run on your host: aws sso login --profile=" + profile)
    return False


def detect_workspace() -> Path:
    """Walk up from CWD to find project root (contains .git + CLAUDE.md)."""
    current = Path.cwd()
    for parent in [current] + list(current.parents):
        if (parent / ".git").exists() and (parent / "CLAUDE.md").exists():
            return parent
        if (parent / ".git").exists() and (parent / "Taskfile.yml").exists():
            return parent
    return current


DEFAULT_COST_TIERS = {
    "Critical": 5000.0,
    "High": 1000.0,
    "Medium": 100.0,
}


def assign_tier(cost: float, thresholds: dict[str, float] | None = None) -> str:
    """Assign cost tier: Critical / High / Medium / Low.

    Args:
        cost: Dollar amount to classify.
        thresholds: Optional override of tier boundaries.
                    Default: Critical > $5K, High > $1K, Medium > $100.
    """
    tiers = thresholds or DEFAULT_COST_TIERS
    if cost >= tiers.get("Critical", 5000.0):
        return "Critical"
    if cost >= tiers.get("High", 1000.0):
        return "High"
    if cost >= tiers.get("Medium", 100.0):
        return "Medium"
    return "Low"


def format_cost(amount: float, currency: str = "USD") -> str:
    """Format cost as locale-aware string with currency symbol.

    Examples: format_cost(1234.56) -> '$1,234.56'
              format_cost(1234.56, 'NZD') -> 'NZ$1,234.56'
    """
    symbols = {"USD": "$", "NZD": "NZ$", "AUD": "A$", "EUR": "€", "GBP": "£"}
    symbol = symbols.get(currency, currency + " ")
    return f"{symbol}{amount:,.2f}"


def build_cli_command(group: str, cmd: str, params: dict[str, Any]) -> list[str]:
    """Build a runbooks CLI command list from parameters dict.

    Args:
        group: CLI group (e.g., "finops", "cert", "inventory", "security")
        cmd: Subcommand (e.g., "dashboard", "triage", "collect", "assess")
        params: Dict of {flag_name: value}. Bool True adds --flag, False skips.
                None values are skipped. Str/int/float become --flag value.

    Returns:
        Command list ready for subprocess.run() or run_cli().
    """
    args = [_runbooks_cmd(), group, cmd]
    for key, val in params.items():
        flag = f"--{key}" if not key.startswith("--") else key
        if val is None or val is False:
            continue
        if val is True:
            args.append(flag)
        else:
            args.extend([flag, str(val)])
    return args


def parse_dashboard_json(json_path: str | Path) -> dict[str, Any]:
    """Parse runbooks finops dashboard JSON export into analysis-ready structure.

    Args:
        json_path: Path to the JSON file produced by runbooks finops dashboard --export json.

    Returns:
        Dict with keys: "services" (list[dict]), "accounts" (list[dict]),
        "summary" (dict), "raw" (original parsed JSON).
        Returns empty structure if file missing or unparseable.
    """
    path = Path(json_path)
    empty = {"services": [], "accounts": [], "summary": {}, "raw": {}}

    if not path.exists():
        print(f"No data file at {path} — authenticate and re-run")
        return empty

    try:
        with open(path) as f:
            raw = json.load(f)
    except (json.JSONDecodeError, OSError) as e:
        print(f"Failed to parse {path}: {e}")
        return empty

    services = raw.get("services", raw.get("cost_by_service", []))
    accounts = raw.get("accounts", raw.get("cost_by_account", []))
    summary = raw.get("summary", raw.get("metadata", {}))
    cost_metric = raw.get("cost_metric", "UnblendedCost")

    return {"services": services, "accounts": accounts, "summary": summary, "cost_metric": cost_metric, "raw": raw}


def render_cost_summary(
    data: list[dict],
    title: str = "Cost Summary",
    cost_key: str = "cost",
    name_key: str = "name",
    cost_metric: str = "UnblendedCost",
) -> None:
    """Render a cost summary as a styled pandas DataFrame with tier coloring.

    Args:
        data: List of dicts with at least cost_key and name_key fields.
        title: Display title for the table.
        cost_key: Key containing the dollar amount.
        name_key: Key containing the item name.
        cost_metric: Cost metric used (shown in title for transparency).
    """
    title = f"{title} ({cost_metric})"
    try:
        import pandas as pd
    except ImportError:
        print(f"--- {title} ---")
        for item in data[:20]:
            print(f"  {item.get(name_key, '?')}: {format_cost(item.get(cost_key, 0))}")
        return

    if not data:
        print(f"{title}: No data available — authenticate and re-run")
        return

    df = pd.DataFrame(data[:20])
    if cost_key in df.columns:
        df["Tier"] = df[cost_key].apply(assign_tier)
        df["Cost"] = df[cost_key].apply(format_cost)

    display_df = df.rename(columns={name_key: "Name", cost_key: "Amount"}) if name_key in df.columns else df

    try:
        from IPython.display import display as ipy_display

        ipy_display(display_df.style.set_caption(title).hide(axis="index"))
    except ImportError:
        print(f"\n{title}")
        print(display_df.to_string(index=False))


def export_evidence(data: dict, notebook_name: str, outpath: str = "tmp/cloud-infrastructure/evidence") -> Path:
    """Export evidence JSON with timestamp and metadata.

    Args:
        data: Evidence data dict to export.
        notebook_name: Name of the source notebook (e.g., 'cert-weekly-report').
        outpath: Output directory (created if needed).

    Returns:
        Path to the written JSON file.
    """
    import hashlib

    out_dir = Path(outpath)
    out_dir.mkdir(parents=True, exist_ok=True)

    timestamp = datetime.now().strftime("%Y-%m-%d")
    filename = f"{notebook_name}-{timestamp}.json"
    filepath = out_dir / filename

    envelope = {
        "metadata": {
            "notebook": notebook_name,
            "timestamp": datetime.now().isoformat(),
            "generator": "runbooks.notebooks.helpers",
        },
        "data": data,
    }

    content = json.dumps(envelope, indent=2, default=str)
    filepath.write_text(content)

    sha256 = hashlib.sha256(content.encode()).hexdigest()
    print(f"Evidence exported: {filepath} (SHA256: {sha256[:16]}...)")
    return filepath


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

    JSON is kept as agent-consumable middle step but NOT exposed to CxO.
    CxO sees: .csv (opens in Excel) + .md (renders in GitHub/Outlook).
    """
    import json as _json

    paths: dict[str, str] = {}
    out = Path(output_dir)
    out.mkdir(parents=True, exist_ok=True)

    # JSON (agent-consumable — hidden from CxO display)
    json_path = out / f"{name}.json"
    json_path.write_text(_json.dumps(data, indent=2, default=str))
    paths["json"] = str(json_path)

    # CSV (CxO-friendly — opens in Excel)
    try:
        df = _data_to_dataframe(data)
        if not df.empty:
            csv_path = out / f"{name}.csv"
            df.to_csv(csv_path, index=False)
            paths["csv"] = str(csv_path)
    except Exception:
        pass  # CSV is best-effort

    # Markdown (CxO-friendly — renders in GitHub/Outlook)
    md_path = out / f"{name}.md"
    md_path.write_text(_data_to_markdown(data, name))
    paths["md"] = str(md_path)

    return paths


def _data_to_dataframe(data: dict):
    """Convert evidence dict to pandas DataFrame for CSV export."""
    import pandas as pd

    if "certificates" in data:
        return pd.DataFrame(data["certificates"])
    if "per_type" in data:
        rows = []
        for rtype, m in data["per_type"].items():
            rows.append(
                {
                    "type": rtype,
                    "v1": m.get("v1", 0),
                    "v2": m.get("v2", 0),
                    "accuracy": m.get("accuracy", 0),
                    "status": m.get("status", "N/A"),
                }
            )
        return pd.DataFrame(rows)
    return pd.DataFrame()


def _data_to_markdown(data: dict, title: str) -> str:
    """Convert evidence dict to business-oriented CxO Markdown report.

    Produces: executive summary, risk-level emoji, actionable tables,
    collapsible detail sections. Renders in GitHub, Outlook, Confluence.
    """
    from datetime import datetime as _dt

    lines: list[str] = []
    ts = _dt.now().strftime("%Y-%m-%d %H:%M")

    if "certificates" in data:
        certs = data["certificates"]
        summary = data.get("summary", {})
        total = len(certs)
        accounts = summary.get(
            "accounts_scanned", len(set(c.get("account_id", "") for c in certs if c.get("account_id")))
        )

        # Categorise
        expired = [c for c in certs if (c.get("days_until_expiry") or 999) <= 0]
        critical = [c for c in certs if 0 < (c.get("days_until_expiry") or 999) <= 7]
        warning = [c for c in certs if 7 < (c.get("days_until_expiry") or 999) <= 30]
        attention = [c for c in certs if 30 < (c.get("days_until_expiry") or 999) <= 90]
        healthy = [c for c in certs if (c.get("days_until_expiry") or 999) > 90]

        # Risk level
        if expired or critical:
            risk, emoji = "CRITICAL", "\U0001f534"
        elif warning:
            risk, emoji = "HIGH", "\U0001f7e0"
        elif attention:
            risk, emoji = "MEDIUM", "\U0001f7e1"
        else:
            risk, emoji = "HEALTHY", "\U0001f7e2"

        lines.append(f"# {emoji} {title.replace('-', ' ').title()}")
        lines.append("")
        lines.append(f"> **Risk Level**: {emoji} **{risk}** | **Generated**: {ts}")
        lines.append("")

        # Executive summary table
        lines.append("## Executive Summary")
        lines.append("")
        lines.append("| Metric | Value |")
        lines.append("|--------|------:|")
        lines.append(f"| Total Certificates | {total} |")
        lines.append(f"| Accounts Scanned | {accounts} |")
        lines.append(f"| \U0001f534 Expired | {len(expired)} |")
        lines.append(f"| \U0001f534 Critical (< 7d) | {len(critical)} |")
        lines.append(f"| \U0001f7e0 Warning (< 30d) | {len(warning)} |")
        lines.append(f"| \U0001f7e1 Attention (< 90d) | {len(attention)} |")
        lines.append(f"| \U0001f7e2 Healthy (> 90d) | {len(healthy)} |")
        lines.append("")

        # Actionable: expiring soon (sorted by urgency)
        expiring = sorted(critical + warning + attention, key=lambda c: c.get("days_until_expiry") or 999)
        if expiring:
            lines.append("## \u26a0\ufe0f Certificates Requiring Action")
            lines.append("")
            lines.append("<details>")
            lines.append(
                f"<summary><strong>{len(expiring)} certificates expiring within 90 days</strong> (click to expand)</summary>"
            )
            lines.append("")
            lines.append("| Domain | Days Left | Account | Account ID | Region | Auto-Renew |")
            lines.append("|--------|----------:|---------|------------|--------|:----------:|")
            for c in expiring[:30]:
                domain = c.get("domain_name") or c.get("domain", "?")
                days = c.get("days_until_expiry", "?")
                acct = c.get("account_name") or c.get("account_id", "?")
                region = c.get("region", "")
                renew = "\u2705" if c.get("auto_renewal_eligible") else "\u274c"
                acct_id = c.get("account_id", "")
                lines.append(f"| {domain} | {days} | {acct} | {acct_id} | {region} | {renew} |")
            lines.append("")
            lines.append("</details>")
            lines.append("")

        # Safe to remove (expired + unused)
        removable = [c for c in expired if not c.get("in_use")]
        if removable:
            lines.append("## \U0001f5d1\ufe0f Certificates Safe to Remove")
            lines.append("")
            lines.append("<details>")
            lines.append(
                f"<summary><strong>{len(removable)} expired unused certificates</strong> (click to expand)</summary>"
            )
            lines.append("")
            lines.append("| Domain | Expired | Account | Account ID | In Use |")
            lines.append("|--------|--------:|---------|------------|:------:|")
            for c in sorted(removable, key=lambda x: x.get("days_until_expiry") or 0)[:20]:
                domain = c.get("domain_name") or c.get("domain", "?")
                days_ago = abs(c.get("days_until_expiry") or 0)
                acct = c.get("account_name") or c.get("account_id", "?")
                acct_id = c.get("account_id", "")
                lines.append(f"| {domain} | {days_ago}d ago | {acct} | {acct_id} | \u274c |")
            lines.append("")
            lines.append("</details>")
            lines.append("")

        # Recommended actions
        lines.append("## \U0001f4cb Recommended Actions")
        lines.append("")
        lines.append(
            f"1. **Renew** {len([c for c in expired if c.get('in_use')])} expired in-use certificates immediately"
        )
        lines.append(f"2. **Schedule** {len(critical)} critical certificates (< 7 days) this sprint")
        lines.append(f"3. **Plan** {len(warning)} warning certificates (< 30 days) for next sprint")
        lines.append(f"4. **Remove** {len(removable)} expired unused certificates (cost cleanup)")
        lines.append("")

    elif "per_type" in data:
        lines.append(f"# \U0001f50d {title.replace('-', ' ').title()}")
        lines.append("")
        lines.append(f"> **Overall Accuracy**: {data.get('overall_accuracy', '?')}% | **Generated**: {ts}")
        lines.append("")
        lines.append("| Type | Config Agg | Resource Explorer | Accuracy | Status |")
        lines.append("|------|----------:|-----------------:|----------:|:------:|")
        for rtype, m in data.get("per_type", {}).items():
            status_emoji = (
                "\u2705" if m.get("status") == "PASS" else "\u26a0\ufe0f" if m.get("status") == "WARN" else "\u274c"
            )
            lines.append(
                f"| {rtype.upper()} | {m.get('v1', '?')} | {m.get('v2', '?')} "
                f"| {m.get('accuracy', '?')}% | {status_emoji} |"
            )
        lines.append("")

    lines.append("---")
    lines.append(f"*Generated {ts} via `runbooks` PyPI | APRA CPS 234 compliance*")
    return "\n".join(lines)
